diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md new file mode 100644 index 0000000000000000000000000000000000000000..d4820420407e5e5232cece0259bd51c8c0244d0b --- /dev/null +++ b/.cursor/BUGBOT.md @@ -0,0 +1,102 @@ +# Bugbot review charter — Budget Router (OpenEnv) + +## North star + +This codebase is an **OpenEnv-style RL / agent environment**: correctness of the +simulation, inference path, and evaluation harness is **non-negotiable**. Treat +every change first as a **risk to invariants**, second as a product of intent. + +**Priority order (strict):** + +1. **Factual and behavioral accuracy** — claims, metrics, seeds, APIs, and + documented procedures must remain true and reproducible. +2. **Regression safety** — no silent change to reward semantics, observation + space, routing contracts, seed selection, or eval aggregation unless + explicitly justified and reflected in docs. +3. **Everything else** — including new features, refactors, and ergonomics — + only after the above are satisfied. + +If a change improves developer experience or adds capability but **weakens +traceability, determinism, or agreement with the published contract**, treat that +as a **defect**, not a win. + +--- + +## Evidence contract + +`README_v1.md` is the **published evidence layer** for this repository: benchmark +definitions, honest scope, statistical reporting, seed buckets, and +environmental assumptions. It is not marketing copy; it is the **external +interface of trust**. + +When reviewing a pull request: + +- Assume reviewers and downstream users will reconcile the diff against + **`README_v1.md`** and the **test suite**, not against intent expressed only in + comments or chat. +- Flag any drift between **implementation**, **eval scripts**, and **documented + claims** as a **primary finding**, not a footnote. +- Prefer **blocking** feedback when the PR could make a true statement in + `README_v1.md` false, ambiguous, or non-reproducible without a coordinated doc + update. + +--- + +## Regression lens (main code and agent path) + +Evaluate from the perspective of **“what breaks for callers?”** — the Gradio / +server surface, the environment stepping contract, inference and routing logic, +and anything an **agent** (heuristic, LLM, or RL policy) depends on. + +Elevate severity when the change touches or could affect: + +- **Reward / termination / budget / SLA semantics** — any path that alters + episode economics without a clear, tested migration story. +- **Observations and action validity** — shapes, bounds, masking, or + interpretation of noisy signals the agent is documented to use. +- **Provider degradation or non-stationarity** — ordering, timing, or randomness + that shifts the task without explicit versioning or changelog discipline. +- **Evaluation** — `eval/` entrypoints, seed handling, aggregation, baselines, and + anything that feeds headline numbers or comparisons in `README_v1.md`. +- **Determinism and auditability** — anything that makes prior results + incomparable across commits without saying so. + +Ask explicitly: **If we merge this, can a user still run the same commands and +obtain a result that is fairly comparable to what the README describes?** If the +answer is “only sometimes” or “only with undocumented flags,” that is a **merge +risk**. + +--- + +## Code review bar + +Hold the diff to a **high-trust research engineering** standard: + +- **Invariants first** — state what must remain true; show how the change + preserves or formally relaxes it. +- **Proof over taste** — prefer runnable tests, property checks, or minimal + reproductions over stylistic preference. Style matters only where it prevents + bugs (e.g., unclear units, magic numbers without provenance). +- **Minimal blast radius** — favor localized, reversible changes; be skeptical of + drive-by refactors bundled with behavioral edits. +- **Failure modes** — consider partial deploys, missing API keys, degraded + backends, and off-by-one episode boundaries as first-class scenarios when + relevant. + +Do **not** optimize review comments for velocity of shipping features. Optimize +for **confidence that main remains a reliable substrate for agents and eval**. + +--- + +## What “approve” means here + +A non-issue or acceptable change is one that **preserves or strengthens** the +truth and stability story relative to `README_v1.md` and existing tests. + +A blocking issue is one that **could** — even in edge cases — produce **wrong +results**, **misleading comparisons**, **undocumented behavior change**, or +**silent regression** in core or agent-facing paths without a commensurate, +explicit update to evidence and tests. + +When uncertain, **assume the worst plausible interpretation** for merge safety, +state the assumption, and recommend what evidence would resolve it. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..d7e01619df75e5e3cb4caa1901662170936cbe30 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +.venv/ +.git/ +__pycache__/ +*.pyc +.pytest_cache/ +.ruff_cache/ +docs/*.png +**/*.png +*.tar.gz +*.egg-info/ +.env +.claude/ +.windsurf/ +.hf_private/ +.DS_Store +artifacts/ +*.zip +*.json +**/*.json +*.txt +**/*.txt + +.hackathon_context/ +README_archived.md +llm_stderr.log +pre_validation.sh +test_docker_step.sh +trained_models/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b774d4569b4794ae1cc7bc72d43a9f1b51c6abc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +.venv/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so +.env +.hf_private/ +.windsurf/ +.matplotlib/ +.DS_Store +*.egg-info/ +dist/ +build/ +*.png +!figures/budget_router_evidence.png +._* +merged_codebase.txt +docs/ +/*.json +*.tar.gz +/*.txt +README_archived.md +llm_stderr.log +pre_validation.sh +test_docker_step.sh +.hackathon_context/ +outputs/*/ +.cursor/ +.docs/ diff --git a/.openenvignore b/.openenvignore new file mode 100644 index 0000000000000000000000000000000000000000..a5558f14c089fee85448755759c7ae9898a76e97 --- /dev/null +++ b/.openenvignore @@ -0,0 +1,8 @@ +*.png +*.tar.gz +README_archived.md +*.json +docs/ +*.txt +*.zip +trained_models/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fa55d9e7f45f9674298e0c8324bac6cd4ed595e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest +FROM ${BASE_IMAGE} AS builder + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y --no-install-recommends git curl && \ + rm -rf /var/lib/apt/lists/* + +COPY . /app/env +WORKDIR /app/env + +RUN if ! command -v uv >/dev/null 2>&1; then \ + curl -LsSf https://astral.sh/uv/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx; \ + fi + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --extra training --no-install-project --no-editable + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --extra training --no-editable + +FROM ${BASE_IMAGE} + +WORKDIR /app + +COPY --from=builder /app/env/.venv /app/.venv +COPY --from=builder /app/env /app/env + +ENV PATH="/app/.venv/bin:$PATH" +ENV PYTHONPATH="/app/env:$PYTHONPATH" + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import os, urllib.request; port = os.environ.get('PORT', '8000'); urllib.request.urlopen(f'http://127.0.0.1:{port}/health', timeout=2)" + +CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port ${PORT:-8000} --proxy-headers --forwarded-allow-ips='*'"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + 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 [yyyy] [name of copyright owner] + + 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/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..34e0133f7b483c13c61a263d271b9250a4f6c62a --- /dev/null +++ b/README.md @@ -0,0 +1,344 @@ +--- +title: "Budget Router" +emoji: "⚙️" +colorFrom: purple +colorTo: indigo +sdk: docker +app_port: 8000 +base_path: /web +pinned: false +--- + +# Budget Router (OpenEnv) + +Budget Router is an OpenEnv-compliant RL environment where an agent routes requests to one of three providers (A/B/C) or sheds load under a tight **cost–reliability–SLA** trade-off. Providers degrade non-stationarily within an episode; the agent observes only a noisy windowed success signal (rolling success rate), not true internal health. + +[![HF Space](https://img.shields.io/badge/🤗-Live%20Demo-yellow)](https://huggingface.co/spaces/akshay4/budget-router-openenv) + +## TL;DR + +**Hard_Multi is the headline scenario**: when Provider A degrades from step 0 and +Provider B cascades at step 10, reactive policies go negative while adaptive ones +stay positive. Three policy families, each stronger than the last, validated +across **30 paired seeds** in three independent buckets (dev, heldout, fresh): + +| Policy | Hard_Multi grader | vs heuristic | Statistical evidence | +|---|---:|---|---| +| Heuristic (reactive) | 0.6076 ± 0.0361 (n=30) | — | — | +| LLM — Qwen2.5-72B + budget-guard | 0.6515 ± 0.0523 (n=30) | **+7.2 %** | Cohen's d = **1.135** (large), paired one-sided p < 1×10⁻⁶, 24/30 wins, bootstrap 95 % CI on Δ = [0.031, 0.058] | +| PPO — SB3, 100k steps | **0.6907 ± 0.0326** (n=10 dev) | **+13.6 %** | 95 % CI [0.667, 0.714], **non-overlapping with heuristic**, 10/10 wins | + +**Mechanism** (PPO): the agent learned to route A→B early and conserve budget +before B's cascade at step 10, pushing `adaptation_score` from 0.6907 (heuristic) +to **0.9328** — a +0.2421 gain on the grader's most diagnostic sub-score. The +LLM achieves a milder version of the same effect (+0.124 adaptation gain +across n=30) by anticipating the cascade in-context. + +**Environment hardness**: heuristic reward goes negative (−2.97) on +Hard_Multi while oracle reaches +4.10 — a 7.07-point gap (≈238 % of the +heuristic's absolute reward) that confirms the cascade task is hard enough +to require RL/in-context reasoning and learnable enough to reward it. + +**Honest scope** (explicitly disclosed): +- The LLM uses a deterministic **budget-safety guard** that vetoes routes which + would bankrupt the budget — a standard agentic-system pattern (LLM for + high-level decisions, deterministic layer for arithmetic-critical safety). + Without the guard, raw LLM occasionally exhausts budget and incurs the −10 + cliff penalty. +- LLM (with guard) wins on **3 of 4 task tiers**: Medium (+5.8 %), Hard (+7.5 %), + Hard_Multi (+11.0 %). Loses Easy by −4.6 % — on a task with no degradation, + the budget-conservative heuristic is near-optimal and the LLM's added + flexibility is unhelpful. +- PPO is trained and evaluated on **Hard_Multi only**; not a general-purpose + policy. This is a deliberate choice — Hard_Multi has a 238 % oracle/heuristic + gap, the largest in the suite, so RL signal is highest there. +- All non-trivial improvement claims come from seeds the policy never saw + during design (heldout 100–109, fresh 200–209). Dev-seed wins are reported + separately and never used to make the headline claim. + +## Run locally +**Enable LLM policy locally**: + +```bash +export API_BASE_URL="https:///v1" # e.g. https://router.huggingface.co/v1 +export API_KEY="" +export MODEL_NAME="" # optional (e.g. Qwen/Qwen2.5-72B-Instruct) +``` + + +```bash +uv sync --extra training +uv run server +``` + +Then open `http://127.0.0.1:8000/web` for the Gradio dashboard. + +To **reproduce or regenerate** the evaluation numbers, traces, PPO workflow, and optional GRPO checks, follow the command checklist in [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md) (companion to the optional `
` blocks below). + + + +To **reproduce or regenerate** the evaluation numbers, traces, PPO workflow, and optional GRPO checks, follow the command checklist in [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md) (companion to the optional `
` blocks below). + + +## Benchmark results + +Three policies evaluated: + +- **Heuristic**: budget-aware, cheapest-viable baseline using only public + observations (`budget_router/policies.py`). +- **LLM**: Qwen2.5-72B via HuggingFace Inference Router, wrapped with a + deterministic budget-safety guard (`inference.py::_apply_budget_safety_guard`). +- **PPO**: MlpPolicy trained with Stable-Baselines3 on Hard_Multi (100k steps, + 4 parallel envs). See `train/train_ppo_hard_multi.py`. +- **Oracle†**: privileged upper-bound with internal-state access, + validation-only, not reported in tables. + +**Dev seeds (0–9), full task suite** — `outputs/freeze_check_alltasks_dev10/eval_summary_*.md`: + +| Task | Heuristic | LLM | PPO | LLM Δ vs heuristic | +|---|---:|---:|---:|---| +| Easy | 0.7718 | 0.7360 | — | −4.6 % *(7 losses, 0 wins, 3 ties)* | +| Medium | 0.6852 | 0.7250 | — | **+5.8 %** *(9 wins, 0 losses, 1 tie)* | +| Hard | 0.6354 | 0.6832 | — | **+7.5 %** *(8 wins, 2 losses, 0 ties)* | +| Hard_Multi | 0.6078 | 0.6746 | **0.6907** | **+11.0 %** *(8 wins, 1 loss, 1 tie)* | + +PPO was trained and evaluated on Hard_Multi only; Easy/Medium/Hard cells are +intentionally blank (no model for those tasks). + +**Statistical evidence — Hard_Multi** (`outputs/freeze_check_*/eval_results_*.json`, +`outputs/ppo_hard_multi_eval.json`): + +| | Heuristic | LLM | PPO | +|---|---|---|---| +| Mean grader | 0.6076 ± 0.0361 (n=30) | 0.6515 ± 0.0523 (n=30) | 0.6907 ± 0.0326 (n=10) | +| Bootstrap 95 % CI | [0.595, 0.620] | [0.633, 0.670] | [0.667, 0.714] | +| Paired Δ vs heuristic | — | +0.0440 (boot 95 % CI [0.031, 0.058]) | +0.0829 | +| **Cohen's d (paired)** | — | **1.135 (LARGE)** | **≈ 2.4 (HUGE)** | +| Paired one-sided p | — | **< 1 × 10⁻⁶** (Welch t = 6.22, df = 29) | (10/10 wins) | +| Sign-test wins / ties / losses | — | **24 / 3 / 3** | 10 / 0 / 0 | +| P(LLM > heuristic) — Agarwal 2021 | — | **0.80** | 1.00 | +| IQM of paired Δ — Agarwal 2021 | — | +0.040 (trimmed 25 %) | — | +| 95 % CI overlap with heuristic | — | None on the Δ | **None on the means** | +| Adaptation sub-score (mean) | 0.6878 | 0.8115 | **0.9328** | + +**Per-bucket reproduction** (each row independent; LLM and heuristic share seeds, +so deltas are paired): + +| Bucket | Seeds | Heuristic | LLM | Δ (rel %) | Wins / Ties / Losses | +|---|---|---:|---:|---:|---:| +| Dev | 0–9 | 0.6078 ± 0.0382 | 0.6746 ± 0.0486 | +0.0668 (+11.0 %) | 8 / 1 / 1 | +| **Heldout** | 100–109 | 0.6064 ± 0.0419 | 0.6454 ± 0.0497 | **+0.0390 (+6.4 %)** | **8 / 2 / 0** | +| **Fresh** | 200–209 | 0.6086 ± 0.0314 | 0.6347 ± 0.0551 | **+0.0261 (+4.3 %)** | **8 / 0 / 2** | +| **Combined non-dev** | 100–109 + 200–209 | 0.6075 | 0.6401 | **+0.0326 (+5.4 %)** | **16 / 2 / 2** | + +![Budget Router Evidence](figures/budget_router_evidence.png) +*Figure: (top-left) LLM advantage grows with task difficulty; (top-right) +three-policy ordering on Hard_Multi with non-overlapping 95% CIs; +(bottom-left) generalization across independent seed buckets including +post-freeze fresh seeds; (bottom-right) adaptation sub-score is the +primary driver of LLM and PPO gains over the reactive heuristic.* + +The fresh-seed bucket (200–209) was added *after* the LLM prompt and budget +guard were frozen. It exists specifically to falsify a "tuned-on-heldout" +critique. The effect persists with no overlap to zero in the bootstrap CI. + +
+🔬 Reproducing PPO Results (Optional) + +The trained PPO policy for the hard_multi scenario is included at +`trained_models/ppo_hard_multi_100k.zip` (143 KB, trained 100k steps). + +To reproduce the 10-seed evaluation locally: + +```bash +# Install dependencies +uv sync --extra training + +# Run evaluation (writes to outputs/ppo_hard_multi_eval.json) +uv run python train/eval_hard_multi.py +``` + +Expected output: PPO mean = 0.691 ± 0.033 vs Heuristic mean = 0.608 ± 0.038, +win_rate = 1.0 (10/10 seeds), non-overlapping 95 % CIs. + +> The deployed `inference.py` uses the LLM policy (Qwen2.5-72B + budget guard) +> as required by the hackathon specification. PPO was trained offline to +> validate environment depth and demonstrate that the task rewards genuine +> RL learning beyond reactive or in-context policies. + +
+ +
+🔬 Reproducing LLM rigorous-stats Results (Optional) + +```bash +# Dev (seeds 0-9), full task suite +uv run python eval/eval_all.py \ + --tasks easy --tasks medium --tasks hard --tasks hard_multi \ + --policies heuristic --policies llm \ + --seeds 10 --seed-set dev \ + --out-dir outputs/freeze_check_alltasks_dev10 + +# Heldout (seeds 100-109), Hard_Multi +uv run python eval/eval_all.py \ + --tasks hard_multi --policies heuristic --policies llm \ + --seeds 10 --seed-set heldout \ + --out-dir outputs/freeze_check_heldout10 + +# Fresh (seeds 200-209), Hard_Multi — uses --seed-values for arbitrary seeds +uv run python eval/eval_all.py \ + --tasks hard_multi --policies heuristic --policies llm \ + --seed-values "200,201,202,203,204,205,206,207,208,209" \ + --out-dir outputs/freeze_check_fresh_200_209 +``` + +All three runs combined produce the n=30 rigorous-stats table above. +Episode-level JSON (per-step actions, rewards, sub-scores) is preserved +under each `outputs/freeze_check_*/` directory. + +
+ +## Why this benchmark has substance + +- **Partial observability**: the agent-visible observation contains only `provider_a/b/c_status`, `budget_remaining`, `queue_backlog`, `system_latency`, and `step_count` (`budget_router/models.py`). True provider health is internal. +- **Non-stationarity**: task difficulty is created by explicit degradation schedules, culminating in Hard_Multi where A degrades from step 0 and B degrades from step 10 (`budget_router/tasks.py`). +- **Coupled constraints**: queue backlog amplifies latency, so routing errors create downstream SLA pressure rather than just local failures (`budget_router/environment.py`). +- **Meaningful evaluation**: the grader separately scores success, latency, budget, SLA, and adaptation; for Hard_Multi, adaptation is explicitly split across the two degradation windows (`budget_router/reward.py`). +- **RL learnability confirmed**: a PPO agent trained from scratch in 100k steps + achieves non-overlapping 95 % CIs above the heuristic on Hard_Multi + (`train/eval_hard_multi.py`), confirming the cascade signal is learnable + beyond reactive or in-context policies. +- **Anti-gaming, anti-overfitting tested**: 41 unit tests + 36 hard validation + assertions including degenerate-policy guards (always-A, always-B, always-shed + all dominated by baseline), grader-exploit guards (pure abstention scores + below 0.40 on Easy), heldout stability checks, and zero-NaN/zero-crash + invariants across 315 episodes. + +### Oracle–Baseline reward gap (verified, n=10 seeds each, dev set) + +| Scenario | Oracle† | Heuristic | Gap | Signal | +|---|---|---|---|---| +| Easy | +10.10 | +6.98 | 3.12 (45 %) | Heuristic competitive | +| Medium | +9.49 | +2.53 | 6.96 (275 %) | Meaningful headroom | +| Hard | +6.54 | +0.88 | 5.66 (643 %) | Heuristic nearly fails | +| **Hard_Multi** | **+4.10** | **−2.97** | **7.07 (238 % of \|baseline\|)** | **Heuristic actively harmful** | + +*† Oracle has privileged access to internal provider health — theoretical ceiling only, not a deployable policy.* + +On Hard_Multi the heuristic reward goes negative (−2.97): the rule-based +policy exhausts budget mid-cascade and actively destroys episode value. +Oracle stays strongly positive (+4.10). The 7.07-point gap — 238 % above the +heuristic's absolute reward — is what produces the large advantage signal that +allows PPO to find a meaningful gradient in 100k steps and the LLM to find a +Cohen's-d ≈ 1.1 effect zero-shot. + +```mermaid +flowchart LR + subgraph Policy["Policy Layer"] + H["Heuristic"] + L["LLM (Qwen2.5-72B + budget guard)"] + P["PPO (SB3, Hard_Multi)"] + end + + subgraph Env["BudgetRouterEnv (OpenEnv)"] + direction TB + O["Observation: provider_statuses, budget, backlog, latency, step"] + A["Actions: route_to_a, route_to_b, route_to_c, shed_load"] + R["Reward: success/fail + cost + SLA penalty, -10 on budget exhaustion"] + G["Episode grader: success, adaptation, latency, budget, SLA"] + O --> A --> R --> G + end + + subgraph Tasks["Task presets"] + E["Easy"] + M["Medium"] + Hd["Hard"] + HM["Hard_Multi (cascade)"] + end + + Policy -->|"action"| Env + Env -->|"obs + reward"| Policy + Tasks -->|"scenario config"| Env +``` + +## Tasks (what changes across difficulty) + +| Task | Budget ($) | Degradation schedule | +|---|---:|---| +| Easy | 1.00 | None (`degradation_start_step=999`) | +| Medium | 0.95 | A degrades after step 5 (`rate=0.15`) | +| Hard | 0.85 | A degrades from step 0 (`rate=0.15`) | +| Hard_Multi | 1.10 | A degrades from step 0 (`rate=0.12`), then B from step 10 (`rate=0.10`) | + +Hard_Multi is the headline scenario: once B starts degrading at step 10, C becomes the only consistently reliable option. Since `cost_c=$0.10/request`, the final 10 steps alone can consume `$1.00` of the `$1.10` budget, making **early budget conservation** a binding constraint. + +## Grader (episode score) + +The episode grader is a weighted score in `[0,1]`: + +`overall = 0.30·success + 0.20·latency + 0.15·budget + 0.15·SLA + 0.20·adaptation` + +Notes (from `budget_router/reward.py`): + +- `success_score` is computed over **all episode steps** (shed-load/abstention is penalized). +- `adaptation_score` evaluates post-degradation success. For Hard_Multi it is a blended window: 0.5×(after A degrades, before B) + 0.5×(after B degrades). + +## Evaluation protocol (reproducibility) + +- **Three independent seed buckets**: dev (0–9) used during policy design; + heldout (100–109) used to falsify dev-seed overfitting; fresh (200–209) + added *after* the LLM and PPO were frozen to falsify "tuned-on-heldout" + concerns. See `eval/eval_all.py::SEED_SETS` and the `--seed-values` CLI + option for arbitrary seed lists. +- **Scripted runs**: `eval/eval_all.py` writes timestamped artifacts under + `outputs/`. Per-episode JSON includes per-step `actions`, `rewards`, and + the full grader sub-score breakdown. +- **Statistical reporting**: We report Cohen's d, paired Welch t-test, + bootstrap 95 % confidence intervals, IQM, and probability of improvement + in line with [Agarwal et al. 2021 (NeurIPS Outstanding Paper)](https://arxiv.org/abs/2108.13264) + and [Henderson et al. 2018](https://arxiv.org/abs/1709.06560)'s reproducibility + recommendations. Sample size n=30 (combined buckets) exceeds the Colas + et al. 2018 recommended power-analysis floor for our observed effect size. +- **Anti-cheating tests**: `budget_router/tests/test_environment.py::TestGraderSemantics` + verifies that pure abstention scores below 0.40 on Easy and that + partial abstention always scores worse than full service. + +## Getting started + +1. Install dependencies: + +```bash +uv sync +``` + +2. (Optional, for LLM policy) set an OpenAI-compatible endpoint: + +```bash +export API_BASE_URL=https://router.huggingface.co/v1 +export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct +export HF_TOKEN=... # or API_KEY +``` + +3. Run evaluation (writes to `outputs/`): + +```bash +# Single-task heldout reproduction +uv run python eval/eval_all.py \ + --tasks hard_multi --seed-set heldout --seeds 10 \ + --policies heuristic --policies llm \ + --out-dir outputs/heldout_repro + +# Full task suite, dev +uv run python eval/eval_all.py \ + --tasks easy --tasks medium --tasks hard --tasks hard_multi \ + --policies heuristic --policies llm \ + --seeds 10 --seed-set dev \ + --out-dir outputs/dev_repro +``` + +## References + +- Altman (1999): *Constrained Markov Decision Processes*. +- Henderson, Islam, Bachman, Pineau, Precup, Meger ([arXiv:1709.06560](https://arxiv.org/abs/1709.06560), AAAI 2018): *Deep Reinforcement Learning that Matters* — foundational reproducibility study; motivated multi-bucket seed evaluation here. +- Colas, Sigaud, Oudeyer ([arXiv:1806.08295](https://arxiv.org/abs/1806.08295), 2018): *How Many Random Seeds? Statistical Power Analysis in Deep RL Experiments* — power-analysis basis for n=30. +- Agarwal, Schwarzer, Castro, Courville, Bellemare ([arXiv:2108.13264](https://arxiv.org/abs/2108.13264), NeurIPS 2021 Outstanding Paper): *Deep RL at the Edge of the Statistical Precipice* — IQM, bootstrap CIs, probability-of-improvement adopted in the statistical-evidence table. diff --git a/REPRODUCIBILITY.md b/REPRODUCIBILITY.md new file mode 100644 index 0000000000000000000000000000000000000000..f1a08c84bec232cd01afba0255841706ae7a473e --- /dev/null +++ b/REPRODUCIBILITY.md @@ -0,0 +1,406 @@ +# Budget Router Reproducibility Guide + +This guide is a Pareto-optimal falsification checklist for Budget Router. Its goal is not to run every possible experiment; it is to quickly answer the questions most likely to invalidate the project claims: + +- Does the environment still behave like the source describes? +- Does the grader still resist reward gaming and abstention exploits? +- Does the heuristic remain a real baseline rather than a degenerate trick? +- Does the LLM policy beat the heuristic for the right reasons, not just prompt or seed overfitting? +- Does PPO still demonstrate learnability beyond reactive heuristics on `hard_multi`? + +Use the active `README.md` only as a claim surface and intuition source. The source of truth is the code: `budget_router/environment.py`, `budget_router/reward.py`, `budget_router/policies.py`, `budget_router/tasks.py`, `inference.py`, `eval/eval_all.py`, `eval/trace_episode.py`, `budget_router/validation.py`, and the tests under `budget_router/tests/`. Do not use archived README files for this analysis. + +## Mental Model + +Budget Router is a partially observable routing environment. A policy chooses one of: + +- `route_to_a` +- `route_to_b` +- `route_to_c` +- `shed_load` + +The policy sees normalized public observations only: provider rolling success estimates, remaining budget, queue backlog, latency, and progress. It does not see true provider health. Provider status `0.5` means unprobed/unknown, not healthy. + +The environment has two scoring layers: + +- Step reward in `budget_router/reward.py::step_reward`: dense learning signal with success/failure, cost, SLA penalty, and a catastrophic budget-exhaustion path in `BudgetRouterEnv.step`. +- Episode grader in `budget_router/reward.py::grade_episode`: semantic benchmark score in `[0, 1]` using success, latency, budget, SLA, and adaptation. + +This distinction matters. Reward hacking usually appears when a policy optimizes a shaped reward or loophole that does not match the semantic grader. The most important checks below are designed to catch that quickly. + +## The 20-30% Command Ladder + +Run these in order when you want high confidence fast. Stop at the first failure and inspect before spending tokens or API calls on larger experiments. + +### 1. Install the Base Environment + +```bash +uv sync +``` + +Why: this is the minimal dependency set for unit tests, heuristic policy checks, environment validation, and non-LLM traces. It does not require API keys or training dependencies. + +Red flags: + +- dependency resolution fails +- imports fail for `openenv_core`, `typer`, or local `budget_router` +- tests below require hidden setup not documented in code + +### 2. Run the Unit and Regression Tests + +```bash +uv run pytest budget_router/tests +``` + +Why: this is the fastest broad guardrail. It covers deterministic resets, observation bounds, reward sanity, anti-abstention grader semantics, `hard_multi` adaptation windows, seed selection, LLM prompt structure, trace output shape, and GRPO reward behavior. + +Highest-value test areas: + +- `test_environment.py::TestGraderSemantics`: catches reward gaming by always shedding or partially abstaining. +- `test_environment.py::TestBehavioralGuards`: catches heuristic budget-exhaustion regressions on `hard_multi`. +- `test_eval_all_seed_selection.py`: catches seed-bucket drift and explicit fresh-seed parsing regressions. +- `test_inference_prompt.py`: catches LLM prompt regressions around budget runway, noise calibration, task name, and bankruptcy warnings. +- `test_grpo_training_reward.py`: catches GRPO reward mistakes where incomplete episodes get full grader credit. + +Red flags: + +- pure abstention scores too high +- partial abstention beats full service +- `hard_multi` adaptation ignores the secondary degradation window +- explicit seeds no longer override named seed sets +- LLM prompt loses `0.500 = unobserved`, budget runway, or bankruptcy constraints +- GRPO partial episodes get the full episode grader + +### 3. Run No-API Environment Validation + +```bash +uv run python -m budget_router.validation +``` + +Why: this compares random, heuristic, oracle, and degenerate policies across tasks and seed sets without calling an LLM. It is the best single command for environment validity, reward-gaming resistance, and oracle-vs-baseline headroom. + +What it checks from source: + +- `random_policy`: lower-bound behavior. +- `heuristic_baseline_policy`: public-observation, cheapest-viable baseline. +- `debug_upper_bound_policy`: oracle/debug policy with privileged internal health access. +- degenerate policies: always A, always B, always C, always shed. +- hard assertions: baseline beats random on core tasks, oracle beats baseline, degenerate policies do not all dominate, heldout behavior is stable, rewards are not NaN, episodes do not exceed 20 steps. + +How to interpret: + +- Oracle above heuristic means the environment has exploitable headroom. +- Heuristic above random means the benchmark is not noise. +- Degenerate policies failing to dominate means the grader is not trivially gameable. +- Heldout stability means basic environment behavior is not seed-fragile. + +Red flags: + +- oracle no longer beats heuristic on any meaningful task +- random beats heuristic broadly outside the intentionally hard `hard_multi` caveat +- always shed or always C dominates the heuristic +- validation passes only because assertions were weakened +- reward means shift sharply without a corresponding intentional source change in `tasks.py`, `environment.py`, or `reward.py` + +### 4. Inspect Exact-Seed Behavior With Traces + +Use traces when aggregate numbers move or when you suspect reward hacking. Start with heuristic because it is deterministic and no-API. + +**Progress while the episode runs:** By default, `eval/trace_episode.py` prints nothing until the episode completes (then it prints the full table and optional JSON). For **~20 sequential LLM calls**, that can look “stuck.” Pass `**--verbose`** or `**-v**` to print one `**[trace]**` line per environment step as it happens (`step`, `action`, step `reward`, cumulative reward, `done`). For `**--policy llm**`, you also get a `**[trace] begin …**` line before the first network call, and `**llm_error=…**` when a step falls back after an API error. + +```bash +uv run python eval/trace_episode.py \ + --task hard_multi \ + --seed 3 \ + --policy heuristic \ + --verbose \ + --output-json outputs/trace_heuristic_hard_multi_seed3.json +``` + +If training extras are installed: use the bundled `trained_models/ppo_hard_multi_100k.zip`, or train from scratch first (overwrites the default save path used by the trace script): + +```bash +uv sync --extra training + +# Recreate the checkpoint from scratch (optional if zip already present and trusted) +uv run python train/train_ppo_hard_multi.py + +uv run python eval/trace_episode.py \ + --task hard_multi \ + --seed 3 \ + --policy ppo \ + --verbose \ + --output-json outputs/trace_ppo_hard_multi_seed3.json +``` + +If API credentials are configured: + +```bash +export API_BASE_URL="https://router.huggingface.co/v1" +export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct" +export HF_TOKEN="" + +uv run python eval/trace_episode.py \ + --task hard_multi \ + --seed 3 \ + --policy llm \ + --verbose \ + --output-json outputs/trace_llm_hard_multi_seed3.json +``` + +Why: After the episode, `eval/trace_episode.py` prints the public observation before each action plus action, provider, success, reward, cumulative reward, cost, budget, latency, and final grader breakdown. With `**--verbose**`, you also see **per-step progress during** the run (recommended for LLM). This is the fastest way to see whether a policy is actually adapting or merely exploiting a scoring artifact. + +Red flags: + +- policy sheds many steps but grader remains high +- policy burns budget early and still scores well +- policy never probes unknown providers but appears to infer hidden health +- LLM repeatedly switches on one noisy failure despite the prompt's noise calibration +- PPO repeatedly chooses a degenerate sequence such as always C or always shed +- traces expose hidden provider health to the acting policy; the trace may display evidence after the fact, but policy inputs should remain public observations + +### 5. Reproduce Heuristic vs LLM Claims by Seed Bucket + +Set credentials only for LLM runs: + +```bash +export API_BASE_URL="https://router.huggingface.co/v1" +export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct" +export HF_TOKEN="" +``` + +Dev full-suite check: + +```bash +uv run python eval/eval_all.py \ + --tasks easy --tasks medium --tasks hard --tasks hard_multi \ + --policies heuristic --policies llm \ + --seeds 10 \ + --seed-set dev \ + --out-dir outputs/repro_dev_alltasks +``` + +Heldout `hard_multi` check: + +```bash +uv run python eval/eval_all.py \ + --tasks hard_multi \ + --policies heuristic --policies llm \ + --seeds 10 \ + --seed-set heldout \ + --out-dir outputs/repro_heldout_hard_multi +``` + +Fresh arbitrary-seed check: + +```bash +uv run python eval/eval_all.py \ + --tasks hard_multi \ + --policies heuristic --policies llm \ + --seed-values "200,201,202,203,204,205,206,207,208,209" \ + --out-dir outputs/repro_fresh_200_209_hard_multi +``` + +Why: `eval/eval_all.py` writes timestamped JSON and Markdown summaries. Its seed logic has explicit named buckets for `dev` and `heldout`, plus `--seed-values` for arbitrary fresh seeds. Fresh seeds are the main defense against "tuned on heldout" critiques. + +How to interpret: + +- Dev is useful for smoke and comparison with existing README claims. +- Heldout is the first real overfitting check. +- Fresh seeds are the strongest quick falsifier of prompt/guard overfitting. +- Compare paired seeds, not just aggregate means; LLM and heuristic should be evaluated on the same seeds. + +Red flags: + +- LLM only wins on dev and collapses on heldout/fresh +- LLM improvement comes mostly from one outlier seed +- LLM loses the `hard_multi` adaptation sub-score while gaining budget score via excessive shedding +- LLM invalid outputs are silently converted to `shed_load` too often +- API/model changes make results incomparable without recording `MODEL_NAME`, endpoint, date, and prompt mode + +Optional raw LLM audit: + +```bash +LLM_LOG_RAW=1 LLM_LOG_RAW_MAX_CHARS=400 \ +uv run python eval/eval_all.py \ + --tasks hard_multi \ + --policies heuristic --policies llm \ + --seed-values "200,201,202" \ + --out-dir outputs/repro_llm_raw_audit +``` + +Why: this helps distinguish real policy behavior from parser/guard artifacts. The parser in `inference.py` extracts a valid action string when present and falls back to `shed_load` when parsing fails. + +### 6. Evaluate the Included PPO Hard_Multi Policy + +```bash +uv sync --extra training + +uv run python train/eval_hard_multi.py +``` + +Why: this is the source-backed PPO comparison path for `hard_multi`. It loads `trained_models/ppo_hard_multi_100k.zip`, evaluates deterministic PPO on seeds `0-9`, evaluates the heuristic on the same seeds, reports mean/std/95% CI/win rate/subscores, and writes `outputs/ppo_hard_multi_eval.json`. + +Red flags: + +- model file is missing +- PPO no longer beats heuristic on most paired seeds +- PPO wins only by budget preservation while success/adaptation collapse +- PPO traces reveal degenerate always-action behavior +- PPO results are compared against a different seed set than heuristic + +Important limitation: `eval/eval_all.py` accepts `--policies ppo` but currently only warns that PPO is not wired there. Use `train/eval_hard_multi.py` or `eval/trace_episode.py --policy ppo` (optional `--verbose`) for PPO evidence. + +### 7. Retrain PPO Only When You Need to Revalidate Learnability + +```bash +uv sync --extra training + +uv run python train/train_ppo_hard_multi.py + +uv run python train/eval_hard_multi.py +``` + +Why: training is expensive relative to the other checks. Run it when source changes touch `environment.py`, `reward.py`, `tasks.py`, `train/gym_wrapper.py`, or PPO hyperparameters. The current training script uses Stable-Baselines3 PPO, `MlpPolicy`, 4 parallel envs, 100k steps, and saves `trained_models/ppo_hard_multi_100k.zip`. + +Red flags: + +- PPO cannot improve after training +- training reward improves but grader does not +- policy learns to terminate early or exploit budget scoring +- learned behavior is strong on dev seeds but weak on exact fresh traces + +### 8. GRPO/Tool-Calling Smoke Checks + +Use this only if you are touching GRPO/training-wrapper code: + +```bash +## blocked for now till we fix GRPO +#uv sync --extra grpo + +#PYTORCH_ENABLE_MPS_FALLBACK=1 uv run python train/smoke_test.py +``` + +Why: this validates model-to-tool-to-environment-to-reward plumbing. It is not evidence of learning. The unit tests around `train/grpo_env.py` and `train/learn_experiment.py` are more important for reward correctness. + +Red flags: + +- model makes no tool calls and receives nonzero reward +- incomplete episodes receive full grader score +- tool wrapper constructs custom history instead of delegating to `BudgetRouterEnv.step` +- action-sequence diversity collapses before learning is expected + +## Policy Definitions + +Heuristic policy: + +- Defined in `budget_router/policies.py::heuristic_baseline_policy`. +- Uses only public `Observation`. +- Chooses the cheapest provider with status above `0.52` or unprobed `0.5`. +- Applies a simple low-budget guard that excludes expensive C below `0.10` budget fraction. +- This is a reactive baseline, not an oracle. + +Oracle/debug upper-bound policy: + +- Defined in `budget_router/policies.py::debug_upper_bound_policy`. +- Uses privileged `InternalState`, including true provider health and remaining budget. +- It is validation-only and should never be presented as a deployable policy. +- Its purpose is to prove there is headroom above the public-observation heuristic. + +LLM policy: + +- Defined in `inference.py::LLMRouter`. +- Uses an OpenAI-compatible chat API. +- Prompt requires exactly one action string. +- Adds trend text, budget runway, task name, and optional previous-step feedback. +- Applies `_apply_budget_safety_guard`, which vetoes actions that would immediately exhaust public remaining budget. +- Parser fallback is `shed_load`; frequent fallback is a red flag, not a win. + +PPO policy: + +- Training path: `train/train_ppo_hard_multi.py`. +- Evaluation path: `train/eval_hard_multi.py`. +- Trace path: `eval/trace_episode.py --policy ppo` (optional `--verbose` / `-v` for per-step lines during the run). +- Gym wrapper: `train/gym_wrapper.py`. +- Current headline PPO scope is `hard_multi`, not all tasks. + +Degenerate policies: + +- Defined in `budget_router/policies.py`. +- Always A, always B, always C, always shed. +- These are not competitors; they are exploit detectors. + +## What Counts as "Results Still Stand" + +The README claims are still credible only if the following all hold: + +1. Unit tests pass, especially grader semantics and seed-selection tests. +2. `budget_router/validation.py` still shows non-triviality, oracle headroom, degenerate-policy resistance, heldout stability, no NaNs, and no >20-step episodes. +3. Exact traces show plausible adaptation rather than abstention, parser fallback, or hidden-state leakage. +4. LLM vs heuristic remains positive on paired heldout and fresh `hard_multi` seeds, not just dev. +5. PPO evaluation through `train/eval_hard_multi.py` still beats heuristic on paired dev seeds if PPO claims are retained. +6. Any material drift is reflected in `README.md`; do not preserve old claims if the source-backed commands contradict them. + +## Fast Failure Triage + +If unit tests fail: + +- Inspect `reward.py` first for grader regressions. +- Inspect `environment.py` next for step history, budget exhaustion, termination, observation bounds, and degradation timing. +- Inspect `tasks.py` if task difficulty or seed outcomes moved unexpectedly. + +If validation fails: + +- Compare random, heuristic, oracle, and degenerate rows. +- If degenerate policies dominate, the grader or task economics are probably gameable. +- If oracle has no headroom, the task is too easy or the oracle/health dynamics changed. +- If heuristic is unstable across seed sets, check degradation jitter and stochastic success paths. + +If LLM results fail: + +- Confirm `MODEL_NAME`, endpoint, prompt mode, and credentials. +- Run a one-seed trace with `--policy llm` (add `--verbose` so each step logs while waiting on the API). +- Enable `LLM_LOG_RAW=1` for a small seed slice. +- Check whether failures are reasoning failures, parser failures, safety-guard interventions, or API/model drift. + +If PPO results fail: + +- Confirm `trained_models/ppo_hard_multi_100k.zip` exists. +- Run `eval/trace_episode.py --policy ppo` (optionally `--verbose`) on a winning and losing seed. +- Check whether `train/gym_wrapper.py` observation/action mapping still matches `BudgetRouterEnv`. +- Retrain only after source-level checks pass. + +## Minimum Evidence Bundle for a PR or Submission + +For a fast but serious evidence package, save outputs from: + +```bash +uv run pytest budget_router/tests + +uv run python -m budget_router.validation + +uv run python eval/trace_episode.py \ + --task hard_multi \ + --seed 3 \ + --policy heuristic \ + --verbose \ + --output-json outputs/evidence_trace_heuristic_hard_multi_seed3.json + +uv run python eval/eval_all.py \ + --tasks hard_multi \ + --policies heuristic --policies llm \ + --seeds 10 \ + --seed-set heldout \ + --out-dir outputs/evidence_heldout_hard_multi + +uv run python eval/eval_all.py \ + --tasks hard_multi \ + --policies heuristic --policies llm \ + --seed-values "200,201,202,203,204,205,206,207,208,209" \ + --out-dir outputs/evidence_fresh_hard_multi + +uv sync --extra training +uv run python train/eval_hard_multi.py +``` + +This bundle covers correctness, anti-gaming, environment validity, exact behavior, heldout/fresh LLM comparison, and PPO learnability. It is small enough to run before a merge, but broad enough to catch most ways the published claims could become false. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app_gradio.py b/app_gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..dc191d60ced36b1471786fbce04ec65c2411adf0 --- /dev/null +++ b/app_gradio.py @@ -0,0 +1,408 @@ +""" +Budget Router — Gradio Visualization Dashboard +Run: python app_gradio.py (launches on http://localhost:7860) +""" +from __future__ import annotations + +import math +import time +from typing import Dict, Optional, Tuple + +import gradio as gr +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.tasks import TASK_PRESETS + +from gradio_ui.config import MAX_STEPS as _MAX_STEPS, POLICY_CHOICES, SCENARIOS +from gradio_ui.policies import get_policy_runner +from gradio_ui.renderers import ( + _kpi_grid, + render_incident_timeline, + render_side_panel, + render_grader_plot, + MISSION_SCORE_HELP, + MISSION_SCORE_LABEL, + _GRADER_PENDING, + _PROVIDER_EMPTY, + render_history_table_compare, +) +from gradio_ui.state import fresh_side_state, _observation_to_dict, record_step +from gradio_ui.theme import LIGHT_CSS, THEME + +MAX_STEPS = _MAX_STEPS + + +# Compatibility: preserve module-level MAX_STEPS for callers. + +# ─── UI Build ───────────────────────────────────────────────────────────────── + +def build_app() -> gr.Blocks: + + def _normalize_seed(seed: object, default: int = 42) -> int: + if seed is None: + return default + try: + val = float(seed) # type: ignore[arg-type] + except Exception: + return default + if math.isnan(val) or math.isinf(val): + return default + try: + return int(val) + except Exception: + return default + + with gr.Blocks(title="Budget Router — Policy Comparison", theme=THEME, css=LIGHT_CSS) as demo: + + left_state = gr.State(fresh_side_state()) + right_state = gr.State(fresh_side_state()) + run_state = gr.State({"running": False, "scenario": "easy", "seed": 42, "step": 0}) + + gr.Markdown( + "# Budget Router — Policy Comparison\n" + "_Select 2 policies · start episode · step or finish episode · compare outcomes_" + ) + + with gr.Row(): + with gr.Column(scale=1): + left_title = gr.Markdown("## Policy A") + left_policy = gr.Dropdown(choices=POLICY_CHOICES, value=None, label="Select policy") + left_status = gr.Textbox(label="Status", interactive=False, lines=2) + left_providers = gr.HTML(_PROVIDER_EMPTY()) + left_budget = gr.HTML("") + left_kpis = gr.HTML( + _kpi_grid( + [ + ("Step", "—"), + ("Last action", "—"), + ("Latency (ms)", "—"), + ("Budget remaining", "—"), + ("Reward", "—"), + ("Adaptation", "—"), + ] + ) + ) + left_badges = gr.HTML("") + left_summary = gr.HTML( + _kpi_grid( + [ + ("Failed %", "—"), + ("SLA breach %", "—"), + ("Avg latency (ms)", "—"), + ] + ) + ) + + with gr.Column(scale=1): + right_title = gr.Markdown("## Policy B") + right_policy = gr.Dropdown(choices=POLICY_CHOICES, value=None, label="Select policy") + right_status = gr.Textbox(label="Status", interactive=False, lines=2) + right_providers = gr.HTML(_PROVIDER_EMPTY()) + right_budget = gr.HTML("") + right_kpis = gr.HTML( + _kpi_grid( + [ + ("Step", "—"), + ("Last action", "—"), + ("Latency (ms)", "—"), + ("Budget remaining", "—"), + ("Reward", "—"), + ("Adaptation", "—"), + ] + ) + ) + right_badges = gr.HTML("") + right_summary = gr.HTML( + _kpi_grid( + [ + ("Failed %", "—"), + ("SLA breach %", "—"), + ("Avg latency (ms)", "—"), + ] + ) + ) + + with gr.Row(): + with gr.Column(scale=2): + gr.Markdown("### Episode Controls") + scenario_sel = gr.Radio(SCENARIOS, value="easy", label="Scenario") + seed_inp = gr.Number(value=42, label="Seed", precision=0) + start_btn = gr.Button("▶ Start Episode", variant="primary", interactive=False) + with gr.Row(): + step_btn = gr.Button("→ Step", variant="secondary", interactive=False) + fast_btn = gr.Button("⚡ Fast-forward", interactive=False) + finish_btn = gr.Button("⏩ Finish Episode", interactive=False) + + gr.Markdown(f"### {MISSION_SCORE_LABEL} (comparison)\n_{MISSION_SCORE_HELP}_") + grader_plot = gr.Plot() + + with gr.Row(elem_classes=["episode-history-row"]): + with gr.Column(scale=1): + left_history_title = gr.Markdown("### Step History — Policy A") + left_history_tbl = gr.HTML(render_history_table_compare([]), elem_classes=["episode-history-table"]) + with gr.Column(scale=1): + right_history_title = gr.Markdown("### Step History — Policy B") + right_history_tbl = gr.HTML(render_history_table_compare([]), elem_classes=["episode-history-table"]) + + with gr.Row(): + with gr.Column(scale=1): + left_grade_title = gr.Markdown(f"### {MISSION_SCORE_LABEL} — Policy A") + left_grade = gr.HTML(_GRADER_PENDING()) + with gr.Column(scale=1): + right_grade_title = gr.Markdown(f"### {MISSION_SCORE_LABEL} — Policy B") + right_grade = gr.HTML(_GRADER_PENDING()) + + gr.Markdown("### Incident Timeline") + incidents_html = gr.HTML(render_incident_timeline("easy")) + + def _render_side(side: Dict, run: Dict, scenario_name: str) -> Tuple[str, str, str, str, str, str, str, str]: + return render_side_panel(side, run, scenario_name) + + def _render_all(ls: Dict, rs: Dict, run: Dict) -> tuple: + scenario_name = str(run.get("scenario", "easy") or "easy") + l_out = _render_side(ls, run, scenario_name) + r_out = _render_side(rs, run, scenario_name) + plot = render_grader_plot( + ls.get("history", []) or [], + rs.get("history", []) or [], + left_name=str(ls.get("policy_name") or ""), + right_name=str(rs.get("policy_name") or ""), + ) + incidents = render_incident_timeline(scenario_name) + + running = bool(run.get("running", False)) + btn_update = gr.update(interactive=running) + config_update = gr.update(interactive=(not running)) + return ( + ls, + rs, + run, + l_out[0], + l_out[1], + l_out[2], + l_out[3], + l_out[4], + l_out[5], + r_out[0], + r_out[1], + r_out[2], + r_out[3], + r_out[4], + r_out[5], + l_out[6], + r_out[6], + l_out[7], + r_out[7], + plot, + incidents, + config_update, + config_update, + config_update, + config_update, + config_update, + btn_update, + btn_update, + btn_update, + ) + + OUTPUTS = [ + left_state, + right_state, + run_state, + left_status, + left_providers, + left_budget, + left_kpis, + left_badges, + left_summary, + right_status, + right_providers, + right_budget, + right_kpis, + right_badges, + right_summary, + left_history_tbl, + right_history_tbl, + left_grade, + right_grade, + grader_plot, + incidents_html, + left_policy, + right_policy, + scenario_sel, + seed_inp, + start_btn, + step_btn, + fast_btn, + finish_btn, + ] + + GRADER_PLOT_IDX = OUTPUTS.index(grader_plot) + + def _update_start_enabled(p1: Optional[str], p2: Optional[str], run: Dict): + left_name = str(p1 or "Policy A") + right_name = str(p2 or "Policy B") + running = bool((run or {}).get("running", False)) + ok = (bool(p1) and bool(p2)) and (not running) + return ( + gr.update(interactive=ok), + f"## {left_name}", + f"## {right_name}", + f"### Step History — {left_name}", + f"### Step History — {right_name}", + f"### {MISSION_SCORE_LABEL} — {left_name}", + f"### {MISSION_SCORE_LABEL} — {right_name}", + ) + + left_policy.change( + _update_start_enabled, + inputs=[left_policy, right_policy, run_state], + outputs=[start_btn, left_title, right_title, left_history_title, right_history_title, left_grade_title, right_grade_title], + ) + right_policy.change( + _update_start_enabled, + inputs=[left_policy, right_policy, run_state], + outputs=[start_btn, left_title, right_title, left_history_title, right_history_title, left_grade_title, right_grade_title], + ) + + scenario_sel.change(lambda s: render_incident_timeline(s), inputs=[scenario_sel], outputs=[incidents_html]) + + def do_start(p1: str, p2: str, scenario: str, seed: Optional[float], _ls: Dict, _rs: Dict, _run: Dict): + ls = fresh_side_state() + rs = fresh_side_state() + + seed_int = _normalize_seed(seed, default=42) + + if not p1 or not p2: + run = {"running": False, "scenario": scenario, "seed": seed_int, "step": 0} + ls["status"] = "Select both policies to start." + rs["status"] = "Select both policies to start." + return _render_all(ls, rs, run) + + runner_l, err_l = get_policy_runner(p1) + runner_r, err_r = get_policy_runner(p2) + if err_l or err_r or runner_l is None or runner_r is None: + ls["status"] = f"❌ {err_l}" if err_l else "" + rs["status"] = f"❌ {err_r}" if err_r else "" + run = {"running": False, "scenario": scenario, "seed": seed_int, "step": 0} + return _render_all(ls, rs, run) + + env_l = BudgetRouterEnv() + env_r = BudgetRouterEnv() + obs_l = env_l.reset(seed=seed_int, scenario=scenario) + obs_r = env_r.reset(seed=seed_int, scenario=scenario) + try: + runner_l.reset(scenario) + except Exception: + pass + try: + runner_r.reset(scenario) + except Exception: + pass + + ls.update( + { + "env": env_l, + "policy_name": p1, + "policy_runner": runner_l, + "obs": _observation_to_dict(obs_l), + "status": f"✅ Running · {p1}", + } + ) + rs.update( + { + "env": env_r, + "policy_name": p2, + "policy_runner": runner_r, + "obs": _observation_to_dict(obs_r), + "status": f"✅ Running · {p2}", + } + ) + run = {"running": True, "scenario": scenario, "seed": seed_int, "step": 0} + return _render_all(ls, rs, run) + + def _apply_local_step(side: Dict, scenario_name: str, global_step: int) -> Dict: + if side.get("done"): + return side + env = side.get("env") + runner = side.get("policy_runner") + if env is None or runner is None: + side["done"] = True + side["status"] = "❌ Not initialized" + return side + try: + action_str = runner.choose_action(side.get("obs", {}) or {}) + except Exception as exc: + side["done"] = True + side["status"] = f"❌ Policy error: {exc}" + return side + + pre_obs = dict(side.get("obs", {}) or {}) + obs_obj = env.step(Action(action_type=ActionType(action_str))) + obs = _observation_to_dict(obs_obj) + reward = float(obs.get("reward", 0.0) or 0.0) + meta = dict(obs.get("metadata", {}) or {}) + done = bool(obs.get("done", False)) + side["history"].append(record_step(global_step, action_str, obs, reward, meta, health_obs=pre_obs)) + side["obs"] = obs + side["cumulative_reward"] = float(side.get("cumulative_reward", 0.0) or 0.0) + reward + side["done"] = done + side["status"] = "✅ Done" if done else str(side.get("status", "")) + return side + + def do_step(ls: Dict, rs: Dict, run: Dict): + if not bool(run.get("running", False)): + return _render_all(ls, rs, run) + if int(run.get("step", 0) or 0) >= MAX_STEPS: + run["running"] = False + return _render_all(ls, rs, run) + + next_step = int(run.get("step", 0) or 0) + 1 + scenario = str(run.get("scenario", "easy") or "easy") + + ls = _apply_local_step(ls, scenario, next_step) + rs = _apply_local_step(rs, scenario, next_step) + run["step"] = next_step + + if next_step >= MAX_STEPS or (ls.get("done") and rs.get("done")): + run["running"] = False + return _render_all(ls, rs, run) + + def _stream_to_end(ls: Dict, rs: Dict, run: Dict): + if not bool(run.get("running", False)): + yield _render_all(ls, rs, run) + return + + frozen = _render_all(ls, rs, run) + frozen_grader_plot = frozen[GRADER_PLOT_IDX] + + while bool(run.get("running", False)) and int(run.get("step", 0) or 0) < MAX_STEPS: + out = do_step(ls, rs, run) + ls, rs, run = out[0], out[1], out[2] + out_list = list(out) + out_list[GRADER_PLOT_IDX] = frozen_grader_plot + yield tuple(out_list) + time.sleep(0.12) + if not bool(run.get("running", False)): + break + + yield _render_all(ls, rs, run) + + def do_fast_forward(ls: Dict, rs: Dict, run: Dict): + yield from _stream_to_end(ls, rs, run) + + def do_finish(ls: Dict, rs: Dict, run: Dict): + yield from _stream_to_end(ls, rs, run) + + start_btn.click(do_start, inputs=[left_policy, right_policy, scenario_sel, seed_inp, left_state, right_state, run_state], outputs=OUTPUTS) + step_btn.click(do_step, inputs=[left_state, right_state, run_state], outputs=OUTPUTS) + fast_btn.click(do_fast_forward, inputs=[left_state, right_state, run_state], outputs=OUTPUTS) + finish_btn.click(do_finish, inputs=[left_state, right_state, run_state], outputs=OUTPUTS) + + return demo + + +if __name__ == "__main__": + app = build_app() + app.queue() + app.launch(server_port=7860) diff --git a/blog.md b/blog.md new file mode 100644 index 0000000000000000000000000000000000000000..0f3e930dcf9661d9550aaa4b3e8e8a0b6e742e28 --- /dev/null +++ b/blog.md @@ -0,0 +1,257 @@ +# Budget Router: Teaching Agents to Survive Cascading API Failures Under Budget + +Production AI systems do not fail politely. + +An application may depend on several LLM or API providers, each with different cost, latency, and reliability profiles. One provider becomes flaky. Traffic shifts. The next fallback becomes overloaded or starts degrading. The system still has a budget, users still expect latency, and the router never sees the true internal health of the providers. It only sees noisy public signals: recent success rates, backlog, latency, and remaining budget. + +That is the problem Budget Router is built to study. + +Budget Router is an OpenEnv-compliant reinforcement learning environment where an agent routes each request to Provider A, B, C, or sheds load. A is cheap, B is moderate, C is reliable but expensive. The agent's job is not simply to pick the best provider now. It must preserve enough budget to survive what happens later. + +The interesting case is `Hard_Multi`: Provider A degrades from the beginning, and Provider B cascades later in the episode. This creates a two-phase incident. A naive router can look reasonable early and still fail late because it spent too much budget before the real cascade arrived. + +This is a small environment, but it captures a real infrastructure question: + +> Can an agent learn budget-aware reliability behavior under partial observability and non-stationary provider degradation? + +## TL;DR + +Budget Router is not a claim that a 20-step toy simulation is production routing. It is a compact, reproducible benchmark for a production-shaped failure mode: budgeted API routing under cascading degradation. + +On the headline `Hard_Multi` task, we compare three policy families: + +| Policy | What it is | Hard_Multi grader | Main takeaway | +|---|---|---:|---| +| Heuristic | Hand-coded reactive baseline | ~0.61 | A real baseline, but brittle under cascade failure | +| Zero-shot LLM | Qwen2.5-72B with a deterministic budget guard | ~0.65 | In-context reasoning helps when observations are semantically meaningful | +| PPO | Small SB3 MLP trained on the environment | ~0.69 | The reward signal is learnable and stronger than hand rules | + +```mermaid +flowchart LR + H["Heuristic baseline
0.61
hand-coded rules"] --> L["Zero-shot LLM
0.65
Qwen2.5-72B + budget guard"] + L --> P["Trained PPO
0.69
SB3 MLP, 100k steps"] +``` + +We also ran post-training experiments beyond PPO: + +- SFT on Qwen2.5-1.5B via Hugging Face Jobs completed end-to-end, but did **not** beat the heuristic on the latest 10-seed evaluation: `0.577` vs `0.596`, with 3/10 wins. +- GRPO was attempted, but did not converge reliably in our setup. +- The negative result is useful: this environment rewards sequential credit assignment, probing, recovery, and budget conservation. Plain behavioral cloning can imitate action patterns without learning why those actions matter. + +![Budget Router evidence](figures/budget_router_evidence.png) + +*Figure: README evidence summary. The strongest claims are the three-policy ordering on `Hard_Multi`, heldout/fresh seed generalization for the LLM, and adaptation-score gains over the reactive heuristic.* + +## The Environment + +Budget Router exposes a simple action space: + +- `route_to_a` +- `route_to_b` +- `route_to_c` +- `shed_load` + +The observation is intentionally public and partial. The policy sees: + +- rolling provider success estimates, +- remaining budget, +- queue backlog, +- system latency, +- episode progress. + +It does **not** see the true hidden provider health. This makes the problem a partially observable decision problem rather than a lookup table. The agent has to infer whether a provider is actually degrading or whether it just saw noise. + +The task suite escalates difficulty: + +| Task | Degradation pattern | Why it matters | +|---|---|---| +| `Easy` | No degradation | Budget-conservative rules are hard to beat | +| `Medium` | A degrades after step 5 | Reactive switching begins to matter | +| `Hard` | A degrades from step 0 | Early adaptation matters | +| `Hard_Multi` | A degrades from step 0, B from step 10 | Cascade failure forces budget-aware anticipation | + +`Hard_Multi` is the core benchmark. If the router burns money on expensive fallbacks too early, it may have no budget left when B starts failing. If it stays cheap for too long, it loses success and SLA. If it sheds load too often, it avoids cost but fails the user. + +That is the point: there is no single dominant action. + +## The Grader + +The episode grader is a weighted score in `[0, 1]`: + +```text +overall = 0.30 * success + + 0.20 * latency + + 0.15 * budget + + 0.15 * SLA + + 0.20 * adaptation +``` + +The grader is designed so that obvious reward hacks are unattractive: + +| Shortcut | Why it fails | +|---|---| +| Always route to C | Good latency, but expensive and budget-risky | +| Always shed load | Avoids cost, but earns no success or adaptation | +| Always use A | Cheap, but collapses once A degrades | +| Switch only after failure | Too late in `Hard_Multi`, because budget and latency errors compound | + +This is best understood as a soft-constraint MDP. Budget and SLA pressure are real and measured, but they are encoded through reward terms rather than enforced through a full constrained-MDP Lagrangian. That distinction matters. The environment is honest about tradeoffs instead of pretending the constraint design is solved. + +## What Worked + +### 1. The heuristic is a real baseline, not a strawman + +The heuristic uses public observations and chooses the cheapest viable provider. It is budget-aware and reactive. On easy settings, this is exactly the kind of policy that should be strong. + +That is important for judge trust. If a learned policy only beats random or a broken baseline, the environment is not very informative. Budget Router's baseline is good enough to make improvement nontrivial, but limited enough that cascade failure exposes its weakness. + +On `Hard_Multi`, the heuristic reaches roughly `0.61`. It is not useless; it is just too reactive for a delayed cascade. + +### 2. Zero-shot LLM routing improves because the state is semantically meaningful + +The LLM policy is not trained on Budget Router. It receives structured observations with meaningful field names: + +```text +provider_a_status: 0.42 +budget_remaining: 0.31 +queue_backlog: 0.20 +system_latency: 0.55 +step_count: 0.60 +``` + +That matters. A language model can reason about "budget remaining," "provider status," and "latency" without gradient updates. The prompt also includes practical routing guidance: do not treat an unprobed `0.500` status as confirmed health, pay attention to trends, and avoid bankruptcy. + +The production-facing LLM policy includes a deterministic budget-safety guard. This is not hidden. It is a deliberate agentic-system pattern: use the model for high-level routing judgment, and use deterministic code for arithmetic-critical safety. Without this guard, raw LLM behavior can sometimes spend itself into the budget cliff. + +On the README's combined `Hard_Multi` evaluation, the LLM improves over the heuristic across dev, heldout, and fresh seed buckets. The important claim is not that the LLM is magical. The claim is that semantically self-describing environments let a foundation model bring useful priors to a new control problem. + +### 3. PPO proves the environment is learnable + +PPO is a small neural policy trained directly on environment interaction. It is not an LLM, and it is not the post-training story. Its role is scientific: if a small policy gradient method can improve over the heuristic, the reward signal has enough structure to optimize. + +The PPO policy uses the same environment mechanics through a Gym wrapper. The wrapper converts OpenEnv-style typed observations into arrays for Stable-Baselines3, but PPO still routes through the same `BudgetRouterEnv.step()` dynamics and grader. + +On `Hard_Multi`, PPO reaches roughly `0.69` and beats the heuristic across the reported seeds. The adaptation sub-score is the clearest mechanism: PPO learns to preserve budget early and route more effectively when the cascade arrives. + +The honest limitation is that PPO sees `step_count`. In a fixed 20-step task, it may learn a schedule keyed partly to the clock: switch away from A early, prepare for B around step 10. That is still useful environment-validation evidence, but it is not the same as proving open-ended reactive reasoning. The LLM result is the stronger evidence for in-context reactive use of semantic observations. + +## What Did Not Work + +The post-training experiments are just as important as the wins. + +### SFT: the pipeline worked, the policy did not improve enough + +We built a full supervised fine-tuning pipeline: + +1. Generate trajectories from a stronger teacher policy. +2. Convert observations and actions into chat-style training examples. +3. Push the dataset to Hugging Face. +4. Train a LoRA adapter on `Qwen/Qwen2.5-1.5B-Instruct` using Hugging Face Jobs. +5. Merge and push the model. +6. Evaluate against the heuristic baseline. + +The operational pipeline worked. The HF Jobs flow trained and evaluated the model on GPU infrastructure. This matters for reproducibility: the fine-tuning path is not a sketch; it is runnable through `generate_sft_data.py`, `train_sft.py`, `eval_sft.py`, and `scripts/submit_sft_hf_jobs.sh`. + +But the latest SFT evaluation did not beat the heuristic. On 10 `Hard_Multi` seeds, SFT scored `0.577` vs heuristic `0.596`, winning 3/10 seeds. + +That is not a result to hide. It is the most useful negative result in the project. + +The likely reason is that behavioral cloning sees only good-looking actions, not the counterfactuals. It can learn "route to B often" or "avoid C when budget is low," but it does not directly learn why a near-miss action is bad, how budget errors compound, or when probing is worth the short-term risk. + +In Budget Router, the objective is episodic. One bad switch can erase a good early trajectory. A static label does not carry the full consequence of that decision. + +### GRPO: promising direction, not a successful result yet + +We also attempted GRPO-style reward optimization for an LLM policy. That is the more natural post-training direction for an OpenEnv agent, because the model can interact with the environment and receive reward from actual consequences. + +In our current run, GRPO did not produce a reliable improvement. The pitch notes reward trending downward, weak rollout quality, and mode collapse in the attempted setup. The practical lesson is that GRPO needs more than a valid environment wrapper. It needs enough reward variance, enough model capacity, stable rollouts, and careful exploration. + +So the honest conclusion is: + +> PPO shows the environment is learnable. Zero-shot LLM shows semantic observations are useful. SFT shows imitation alone is not enough. GRPO remains the right research direction, but not a claimed win in this submission. + +## Why This Is Still a Strong Result + +The strongest version of Budget Router is not "we found one trick that wins." It is this: + +```mermaid +flowchart TD + E["OpenEnv environment
partial observability + cascade failure"] --> G["Five-part grader
success, latency, budget, SLA, adaptation"] + G --> B["Heuristic baseline
cheap reactive policy"] + G --> L["Zero-shot LLM
semantic reasoning + budget guard"] + G --> P["PPO
reward-aware optimization"] + P --> S["SFT/GRPO attempts
negative results and future direction"] +``` + +Budget Router has the properties a useful post-training environment should have: + +| Property | Evidence | +|---|---| +| Non-trivial | Heuristic beats random but leaves headroom; oracle gap is largest on `Hard_Multi` | +| Learnable | PPO improves over heuristic on the hardest task | +| Semantically agentic | Zero-shot LLM improves because observations are meaningful | +| Not trivially gameable | Always-shed and always-expensive policies are penalized | +| Reproducible | README and `REPRODUCIBILITY.md` describe seed buckets, traces, saved JSON, and command paths | +| Honest | SFT and GRPO attempts are reported without overstating them | + +That combination is rare in hackathon environments. Many environments are easy to demo but hard to falsify. Budget Router is designed to be falsified: run the seeds, inspect the traces, compare sub-scores, and check whether improvement comes from adaptation rather than a loophole. + +## Reproducibility + +The repo is structured so judges can inspect both aggregate results and exact behavior. + +Key artifacts: + +- `README.md`: headline benchmark tables and evidence figure. +- `REPRODUCIBILITY.md`: command checklist and falsification guide. +- `eval/eval_all.py`: heuristic vs LLM evaluation across task and seed buckets. +- `eval/trace_episode.py`: step-by-step episode traces. +- `train/eval_hard_multi.py`: PPO evaluation path. +- `generate_sft_data.py`: SFT dataset generation from teacher trajectories. +- `train_sft.py`: LoRA SFT training script for Hugging Face Jobs. +- `eval_sft.py`: SFT model evaluation against the heuristic. +- `scripts/submit_sft_hf_jobs.sh`: orchestration for data, training, and evaluation jobs. + +For the SFT pipeline, the intended run looks like: + +```bash +export TEACHER_POLICY=ppo +export HF_JOB_FLAVOR=a10g-large +export HF_JOB_NAMESPACE=akshay4 +export DATASET_REPO=akshay4/budget-router-sft-data +export OUTPUT_REPO=akshay4/budget-router-sft-qwen1.5b +export SFT_MODEL_REPO=$OUTPUT_REPO +export SFT_N_EPISODES=100 +export SFT_TOP_FRACTION=0.30 +export NUM_EPOCHS=3 +export N_SEEDS=10 + +./scripts/submit_sft_hf_jobs.sh +``` + +The important point is not that this SFT model won. It did not. The important point is that the environment can produce training data, launch model training, push artifacts, and evaluate the resulting policy. That closes the environment-to-training-to-evaluation loop, even when the experimental result is negative. + +## The Research Lesson + +Budget Router is a reminder that post-training methods should match the task. + +For static classification, supervised fine-tuning may be enough. For sequential decision-making under budget constraints, static imitation is often too weak. The agent needs to learn from consequences: what happens after a risky fallback, what happens when it fails to probe, what happens when it saves budget early, and what happens when it arrives at the cascade with no runway left. + +That is why PPO worked better than SFT here. PPO receives feedback from the environment. It optimizes the episode objective directly. The zero-shot LLM also performs well because it brings external priors about risk, cost, and reliability to a semantically described state. + +The next research step is not to pretend SFT solved the problem. It is to use SFT as a warm start or distillation layer, then apply environment-aware RL with better rollout diversity and reward normalization. + +## Conclusion + +Budget Router is an incident-commander environment for budgeted API reliability. It asks a simple question with real consequences: + +> When providers degrade and budget is running out, can an agent adapt before the cascade breaks the system? + +The answer from our experiments is nuanced: + +- hand-coded rules are strong but brittle, +- zero-shot LLM reasoning helps when the observation schema is meaningful, +- PPO confirms the environment has a learnable reward signal, +- SFT and GRPO are not claimed wins, but they reveal where the hard part actually is. + +That is the story we think is worth submitting: a reproducible environment, a real baseline, measurable improvement, and enough intellectual honesty that the failures make the benchmark more credible rather than less. diff --git a/budget_router/__init__.py b/budget_router/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c4ac9805cd6b05d0c4e739f0bb430ee80059efdc --- /dev/null +++ b/budget_router/__init__.py @@ -0,0 +1,17 @@ +"""Budget Router Environment - package init.""" + +from .environment import BudgetRouterEnv +from .models import Action, ActionType, EnvState, Observation, TaskConfig +from .tasks import EASY, HARD, MEDIUM + +__all__ = [ + "BudgetRouterEnv", + "Action", + "ActionType", + "Observation", + "EnvState", + "TaskConfig", + "EASY", + "MEDIUM", + "HARD", +] diff --git a/budget_router/client.py b/budget_router/client.py new file mode 100644 index 0000000000000000000000000000000000000000..06699b0dd83bf1d36c8538dad6cee91413a525e3 --- /dev/null +++ b/budget_router/client.py @@ -0,0 +1,29 @@ +from dataclasses import asdict +from typing import Any, Dict + +from openenv_core import HTTPEnvClient +from openenv_core.client_types import StepResult + +from .models import Action, EnvState, Observation + + +class BudgetRouterClient(HTTPEnvClient[Action, Observation]): + def _step_payload(self, action: Action) -> Dict[str, Any]: + return asdict(action) + + def _parse_result(self, payload: Dict[str, Any]) -> StepResult[Observation]: + observation_payload = payload.get("observation", payload) + observation = Observation( + **observation_payload, + done=payload.get("done", observation_payload.get("done", False)), + reward=payload.get("reward", observation_payload.get("reward")), + metadata=observation_payload.get("metadata", payload.get("metadata", {})), + ) + return StepResult( + observation=observation, + reward=observation.reward, + done=observation.done, + ) + + def _parse_state(self, payload: Dict[str, Any]) -> EnvState: + return EnvState(**payload) diff --git a/budget_router/environment.py b/budget_router/environment.py new file mode 100644 index 0000000000000000000000000000000000000000..325327c774f6fc650c6b55a192e9c1edb2f6475d --- /dev/null +++ b/budget_router/environment.py @@ -0,0 +1,515 @@ +""" +Budget Router Environment — Core RL environment. + +Extends openenv-core Environment base class with the standard +reset(), step(), state interface. Processes one request per step +through 3 providers under budget, latency, reliability, and +degradation constraints. +""" + +from __future__ import annotations + +import json +import math +import random +import uuid +from typing import Any, Dict, Optional, Tuple + +from openenv_core.env_server import Environment +from openenv_core.env_server.types import Action as OpenEnvAction + +from .models import ( + Action, + ActionType, + EnvState, + InternalState, + Observation, + ProviderState, + TaskConfig, +) +from .reward import grade_episode, step_reward +from .tasks import EASY + +BACKLOG_LATENCY_PER_ITEM_MS = 8.0 + + +def _reported_score(value: float) -> float: + return min(max(float(value), 0.001), 0.999) + + +class BudgetRouterEnv(Environment): + """ + Incident Commander for Budgeted Tool/API Reliability. + + An agent routes incoming requests to one of 3 providers (A, B, C) + or sheds load, under budget, latency, and reliability constraints. + + Extends OpenEnv Environment base class with proper type parameters. + + Interface: + reset(seed, scenario) -> Observation + step(action) -> Observation (reward in obs.reward, done in obs.done) + state -> EnvState + """ + + def __init__(self, emit_structured_logs: bool = False) -> None: + super().__init__() + self._internal: InternalState = InternalState() + self._config: TaskConfig = EASY + self._rng: random.Random = random.Random() + self._episode_id: str = "" + self._cumulative_reward: float = 0.0 + self._emit_structured_logs = emit_structured_logs + self._episode_number = 0 + self._current_seed: Optional[int] = None + + def _emit_log(self, prefix: str, payload: Dict[str, Any]) -> None: + if self._emit_structured_logs: + print(f"{prefix} {json.dumps(payload)}", flush=True) + + def _observation_payload(self, observation: Observation) -> Dict[str, float]: + return { + "provider_a_status": float(observation.provider_a_status), + "provider_b_status": float(observation.provider_b_status), + "provider_c_status": float(observation.provider_c_status), + "budget_remaining": float(observation.budget_remaining), + "queue_backlog": float(observation.queue_backlog), + "system_latency": float(observation.system_latency), + "step_count": float(observation.step_count), + } + + # ─── OpenEnv interface ────────────────────────────────────────────── + + def reset( + self, + seed: Optional[int] = None, + episode_id: Optional[str] = None, + scenario: Optional[TaskConfig] = None, + **kwargs: Any, + ) -> Observation: + """Reset the environment to initial state.""" + config = scenario or kwargs.get("scenario", EASY) + if isinstance(config, str): + from .tasks import TASK_PRESETS + + config = TASK_PRESETS.get(config, EASY) + self._config = config + + # Seed the RNG + if seed is not None: + self._rng = random.Random(seed) + else: + self._rng = random.Random() + + self._episode_id = episode_id or str(uuid.uuid4()) + self._episode_number += 1 + self._current_seed = seed + self._cumulative_reward = 0.0 + + # Initialize providers + providers = { + "A": ProviderState( + name="A", + base_reliability=config.reliability_a, + current_health=config.reliability_a, + cost_per_request=config.cost_a, + base_latency_ms=config.latency_a, + ), + "B": ProviderState( + name="B", + base_reliability=config.reliability_b, + current_health=config.reliability_b, + cost_per_request=config.cost_b, + base_latency_ms=config.latency_b, + ), + "C": ProviderState( + name="C", + base_reliability=config.reliability_c, + current_health=config.reliability_c, + cost_per_request=config.cost_c, + base_latency_ms=config.latency_c, + ), + } + + # Resolve jittered degradation onsets for this episode + _j1 = (self._rng.randint(-config.degradation_start_jitter, + config.degradation_start_jitter) + if config.degradation_start_jitter > 0 else 0) + _j2 = (self._rng.randint(-config.secondary_degradation_start_jitter, + config.secondary_degradation_start_jitter) + if config.secondary_degradation_start_jitter > 0 else 0) + _actual_primary = max(0, config.degradation_start_step + _j1) + _actual_secondary = max(0, config.secondary_degradation_start_step + _j2) + + self._internal = InternalState( + providers=providers, + budget_dollars=config.initial_budget, + initial_budget_dollars=config.initial_budget, + queue_backlog_count=0, + max_queue_backlog=config.max_queue_backlog, + last_latency_ms=config.latency_a, # initial non-zero latency + sla_ceiling_ms=config.sla_ceiling_ms, + current_step=0, + max_steps=config.max_steps, + episode_done=False, + history=[], + provider_window={"A": [], "B": [], "C": []}, + window_size=5, + actual_degradation_start=_actual_primary, + actual_secondary_degradation_start=_actual_secondary, + ) + + observation = self._get_obs() + self._emit_log( + "[START]", + { + "task": self._config.name, + "seed": int(seed) if seed is not None else -1, + "episode": self._episode_number, + }, + ) + return observation + + def step( + self, + action: OpenEnvAction, + timeout_s: Optional[float] = None, + **kwargs: Any, + ) -> Observation: + """ + Execute one step: route a request or shed load. + + Returns: + Observation with reward set, done flag, and metadata dict. + """ + if self._internal.episode_done: + # Already done — return terminal observation + obs = self._get_obs() + obs.done = True + obs.reward = 0.0 + return obs + + if not isinstance(action, Action): + action = Action( + action_type=getattr(action, "action_type"), + metadata=getattr(action, "metadata", {}), + ) + + if not self._internal.providers: + self.reset(seed=self._current_seed, scenario=self._config) + + self._internal.current_step += 1 + action_type = action.action_type.value + + # ── Apply degradation BEFORE processing the request ── + self._degrade() + + # ── Process the action ── + step_info: Dict[str, Any] = { + "step": self._internal.current_step, + "action_type": action_type, + "sla_ceiling_ms": self._config.sla_ceiling_ms, + "initial_budget": self._internal.initial_budget_dollars, + "degradation_start_step": self._internal.actual_degradation_start, + "secondary_degradation_start_step": (self._internal.actual_secondary_degradation_start + if self._config.secondary_degradation_target else None), + } + + if action_type == "shed_load": + # Shed load: no routing, flat penalty + reward = step_reward( + action_type="shed_load", + request_succeeded=False, + provider_cost=0.0, + initial_budget=self._internal.initial_budget_dollars, + latency_ms=0.0, + sla_ceiling_ms=self._config.sla_ceiling_ms, + ) + # Queue pressure decreases slightly when shedding + self._internal.queue_backlog_count = max( + 0, self._internal.queue_backlog_count - 1 + ) + # Latency set to 0 for shed (no request processed) + self._internal.last_latency_ms = 0.0 + + step_info.update( + { + "request_succeeded": False, + "cost": 0.0, + "latency_ms": 0.0, + "reward": reward, + "provider": None, + "queue_overflow": False, + } + ) + + else: + # Route to a provider + provider_name = {"route_to_a": "A", "route_to_b": "B", "route_to_c": "C"}[ + action_type + ] + provider = self._internal.providers[provider_name] + self._internal.probed_providers.add(provider_name) + + # Deduct cost + cost = provider.cost_per_request + self._internal.budget_dollars -= cost + + # Check budget exhaustion + if self._internal.budget_dollars <= 0: + self._internal.budget_dollars = max(0.0, self._internal.budget_dollars) + # Terminal penalty + reward = -10.0 + self._internal.episode_done = True + self._internal.last_latency_ms = 0.0 + + step_info.update( + { + "request_succeeded": False, + "cost": cost, + "latency_ms": 0.0, + "reward": reward, + "provider": provider_name, + "queue_overflow": False, + "budget_exhausted": True, + } + ) + + self._internal.history.append(step_info) + self._cumulative_reward += reward + + obs = self._get_obs() + obs.done = True + obs.reward = reward + obs.metadata = step_info + self._emit_log( + "[STEP]", + { + "step": self._internal.current_step, + "action": action_type, + "reward": float(reward), + "done": bool(obs.done), + "observation": self._observation_payload(obs), + }, + ) + self._emit_log( + "[END]", + { + "task": self._config.name, + "seed": int(self._current_seed) if self._current_seed is not None else -1, + "episode": self._episode_number, + "total_reward": round(float(self._cumulative_reward), 4), + "score": _reported_score(float(grade_episode(self._internal.history)["overall_score"])), + }, + ) + return obs + + # Determine if request succeeds (based on current_health) + request_succeeded = self._rng.random() < provider.current_health + provider.total_requests += 1 + + # Update windowed tracking + window = self._internal.provider_window[provider_name] + window.append(request_succeeded) + if len(window) > self._internal.window_size: + window.pop(0) + + if request_succeeded: + provider.successful_requests += 1 + + # Compute latency + base_lat = provider.base_latency_ms + noise = self._rng.gauss(0, self._config.latency_noise_std) + # Queue backlog amplifies latency multiplicatively. + # At max backlog (norm=1.0), latency increases by 50%. + # This makes queue_backlog a causally relevant observation + # by indirectly coupling it to reward via SLA breaches. + queue_norm = ( + self._internal.queue_backlog_count / self._internal.max_queue_backlog + if self._internal.max_queue_backlog > 0 else 0.0 + ) + backlog_amplifier = 1.0 + 0.5 * queue_norm + # Failed requests have higher latency (timeout-like behavior) + if not request_succeeded: + actual_latency = (base_lat + abs(noise) + 200.0) * backlog_amplifier + else: + actual_latency = max(10.0, (base_lat + noise) * backlog_amplifier) + self._internal.last_latency_ms = actual_latency + + # Queue backlog: failures increase pressure + queue_overflow = False + if not request_succeeded: + self._internal.queue_backlog_count = min( + self._internal.max_queue_backlog, + self._internal.queue_backlog_count + 2, + ) + if ( + self._internal.queue_backlog_count + >= self._internal.max_queue_backlog + ): + queue_overflow = True + else: + # Successful request drains queue slightly + self._internal.queue_backlog_count = max( + 0, self._internal.queue_backlog_count - 1 + ) + + # Compute reward + reward = step_reward( + action_type=action_type, + request_succeeded=request_succeeded, + provider_cost=cost, + initial_budget=self._internal.initial_budget_dollars, + latency_ms=actual_latency, + sla_ceiling_ms=self._config.sla_ceiling_ms, + ) + + step_info.update( + { + "request_succeeded": request_succeeded, + "cost": cost, + "latency_ms": round(actual_latency, 2), + "reward": reward, + "provider": provider_name, + "queue_overflow": queue_overflow, + } + ) + + # ── Record history ── + self._internal.history.append(step_info) + self._cumulative_reward += reward + + # ── Check episode end ── + if self._internal.current_step >= self._internal.max_steps: + self._internal.episode_done = True + + # ── Build observation ── + obs = self._get_obs() + obs.done = self._internal.episode_done + obs.reward = reward + obs.metadata = step_info + + self._emit_log( + "[STEP]", + { + "step": self._internal.current_step, + "action": action_type, + "reward": float(reward), + "done": bool(obs.done), + "observation": self._observation_payload(obs), + }, + ) + + if obs.done: + self._emit_log( + "[END]", + { + "task": self._config.name, + "seed": int(self._current_seed) if self._current_seed is not None else -1, + "episode": self._episode_number, + "total_reward": round(float(self._cumulative_reward), 4), + "score": _reported_score(float(grade_episode(self._internal.history)["overall_score"])), + }, + ) + + return obs + + @property + def state(self) -> EnvState: + """OpenEnv-compatible state property.""" + return EnvState( + episode_id=self._episode_id, + step_count=self._internal.current_step, + scenario_name=self._config.name, + is_done=self._internal.episode_done, + ) + + # ─── Internal methods ────────────────────────────────────────────── + + def _get_obs(self) -> Observation: + """Convert internal state to normalized [0,1] observation.""" + s = self._internal + + # Provider status: 0.5 for unprobed (max uncertainty), windowed rate if probed + def _probed_status(name: str) -> float: + if name not in s.probed_providers: + return 0.5 + return s.get_windowed_success_rate(name) + + a_status = _probed_status("A") + b_status = _probed_status("B") + c_status = _probed_status("C") + + # Budget: fraction remaining + if s.initial_budget_dollars > 0: + budget_frac = max(0.0, s.budget_dollars / s.initial_budget_dollars) + else: + budget_frac = 0.0 + + # Queue backlog: normalized + if s.max_queue_backlog > 0: + queue_norm = s.queue_backlog_count / s.max_queue_backlog + else: + queue_norm = 0.0 + + # Latency: normalized to SLA ceiling + if s.sla_ceiling_ms > 0: + latency_norm = s.last_latency_ms / s.sla_ceiling_ms + else: + latency_norm = 0.0 + + # Step progress + if s.max_steps > 0: + step_norm = s.current_step / s.max_steps + else: + step_norm = 0.0 + + return Observation( + provider_a_status=max(0.0, min(1.0, a_status)), + provider_b_status=max(0.0, min(1.0, b_status)), + provider_c_status=max(0.0, min(1.0, c_status)), + budget_remaining=max(0.0, min(1.0, budget_frac)), + queue_backlog=max(0.0, min(1.0, queue_norm)), + system_latency=max(0.0, min(1.0, latency_norm)), + step_count=max(0.0, min(1.0, step_norm)), + ) + + def _degrade(self) -> None: + """ + Apply stochastic degradation to configured provider(s). + + The target provider's health decreases based on: + - degradation_rate from the TaskConfig + - A small random perturbation + - Only triggers after actual_degradation_start (jittered per episode) + Supports secondary degradation for multi-provider scenarios. + """ + config = self._config + step = self._internal.current_step + + # Primary degradation + if step >= self._internal.actual_degradation_start: + target = config.degradation_target + provider = self._internal.providers.get(target) + if provider is not None: + noise = self._rng.gauss(0, 0.02) + health_reduction = config.degradation_rate + noise + provider.current_health = max( + 0.05, + provider.current_health - health_reduction, + ) + + # Secondary degradation (for multi-provider scenarios) + if ( + config.secondary_degradation_target + and step >= self._internal.actual_secondary_degradation_start + ): + target = config.secondary_degradation_target + provider = self._internal.providers.get(target) + if provider is not None: + noise = self._rng.gauss(0, 0.02) + health_reduction = config.secondary_degradation_rate + noise + provider.current_health = max( + 0.05, + provider.current_health - health_reduction, + ) diff --git a/budget_router/models.py b/budget_router/models.py new file mode 100644 index 0000000000000000000000000000000000000000..b07ec417f3eb467bcca387732a2e6c34dfe8c780 --- /dev/null +++ b/budget_router/models.py @@ -0,0 +1,212 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Dict, List, Literal, Optional + +from openenv_core.env_server.types import ( + Action as BaseAction, + Observation as BaseObservation, + State as BaseState, +) + + +# ============================================================================= +# Action — extends OpenEnv Action +# ============================================================================= + + +class ActionType(str, Enum): + """The four possible routing actions.""" + + ROUTE_TO_A = "route_to_a" + ROUTE_TO_B = "route_to_b" + ROUTE_TO_C = "route_to_c" + SHED_LOAD = "shed_load" + + +@dataclass(kw_only=True) +class Action(BaseAction): + """ + Agent action: route a request to a provider or shed load. + + Extends OpenEnv Action (which provides `metadata` field). + """ + + action_type: Literal["route_to_a", "route_to_b", "route_to_c", "shed_load"] + + def __post_init__(self) -> None: + if isinstance(self.action_type, str): + self.action_type = ActionType(self.action_type) + + +# ============================================================================= +# Observation — extends OpenEnv Observation +# ============================================================================= + + +@dataclass(kw_only=True) +class Observation(BaseObservation): + """ + Agent-visible observation. ALL numeric fields are normalized to [0.0, 1.0]. + + Extends OpenEnv Observation (which provides `done`, `reward`, `metadata` fields). + """ + + # Provider health (recent success rates) + provider_a_status: float + provider_b_status: float + provider_c_status: float + + # Resource state + budget_remaining: float + queue_backlog: float + system_latency: float + + # Episode progress + step_count: float + + def __post_init__(self) -> None: + for field_name in ( + "provider_a_status", + "provider_b_status", + "provider_c_status", + "budget_remaining", + "queue_backlog", + "system_latency", + "step_count", + ): + setattr(self, field_name, max(0.0, min(1.0, getattr(self, field_name)))) + + +# ============================================================================= +# Internal State (raw units, for debugging / trace only) +# ============================================================================= + + +@dataclass +class ProviderState: + """Internal state of a single provider in raw units.""" + + name: str + base_reliability: float # initial reliability [0, 1] + current_health: float # current health [0, 1] + cost_per_request: float # dollars + base_latency_ms: float # base latency in ms + total_requests: int = 0 + successful_requests: int = 0 + + @property + def observed_success_rate(self) -> float: + """Success rate from agent's perspective (windowed).""" + if self.total_requests == 0: + return self.base_reliability + return self.successful_requests / self.total_requests + + +@dataclass +class InternalState: + """ + Full internal state in raw units. NOT exposed to the agent. + Used for manual trace, debugging, and the oracle policy. + """ + + providers: Dict[str, ProviderState] = field(default_factory=dict) + budget_dollars: float = 0.0 + initial_budget_dollars: float = 0.0 + queue_backlog_count: int = 0 + max_queue_backlog: int = 10 + last_latency_ms: float = 0.0 + sla_ceiling_ms: float = 500.0 + current_step: int = 0 + max_steps: int = 20 + episode_done: bool = False + history: List[Dict[str, Any]] = field(default_factory=list) + + # Windowed success tracking (last N requests per provider) + provider_window: Dict[str, List[bool]] = field(default_factory=dict) + window_size: int = 5 + + # Probed providers: tracks which providers have been routed to at least once + probed_providers: set = field(default_factory=set) + + # Resolved (jittered) degradation onsets for this episode + actual_degradation_start: int = 0 + actual_secondary_degradation_start: int = 999 + + def get_windowed_success_rate(self, provider_name: str) -> float: + """Get success rate over the last `window_size` requests for a provider.""" + window = self.provider_window.get(provider_name, []) + if not window: + return self.providers[provider_name].base_reliability + return sum(window) / len(window) + + +# ============================================================================= +# Task Configuration +# ============================================================================= + + +@dataclass +class TaskConfig: + """ + Configuration for a task scenario. Passed to reset(scenario=config). + NOT a subclass — just a data container. + """ + + name: str + description: str + + # Budget + initial_budget: float = 5.0 # dollars + + # Provider costs (per request, dollars) + cost_a: float = 0.01 + cost_b: float = 0.05 + cost_c: float = 0.10 + + # Provider base reliability + reliability_a: float = 0.70 + reliability_b: float = 0.90 + reliability_c: float = 0.99 + + # Provider base latency (ms) + latency_a: float = 100.0 + latency_b: float = 150.0 + latency_c: float = 200.0 + + # SLA + sla_ceiling_ms: float = 500.0 + + # Degradation config (primary) + degradation_start_step: int = 0 # step at which degradation begins + degradation_rate: float = 0.0 # health reduction per step for provider A + degradation_target: str = "A" # which provider degrades + degradation_start_jitter: int = 0 # ±jitter applied per episode to degradation_start_step + + # Secondary degradation (for multi-provider scenarios) + secondary_degradation_start_step: int = 999 # 999 = no secondary degradation + secondary_degradation_rate: float = 0.0 + secondary_degradation_target: str = "" # empty = no secondary degradation + secondary_degradation_start_jitter: int = 0 # ±jitter applied per episode to secondary_degradation_start_step + + # Episode + max_steps: int = 20 + max_queue_backlog: int = 10 + + # Stochastic noise + latency_noise_std: float = 30.0 # ms std dev added to base latency + + +# ============================================================================= +# OpenEnv State — extends BaseState +# ============================================================================= + + +@dataclass +class EnvState(BaseState): + """ + OpenEnv-compatible state object returned by the `state` property. + Extends BaseState (which provides `episode_id`, `step_count` fields). + """ + + scenario_name: str = "" + is_done: bool = False diff --git a/budget_router/policies.py b/budget_router/policies.py new file mode 100644 index 0000000000000000000000000000000000000000..a0050a25d7e637b5c7bc1f4e9724b6fc8a10715b --- /dev/null +++ b/budget_router/policies.py @@ -0,0 +1,141 @@ +""" +Policies for the Budget Router environment. + +6 policies: +- random_policy: uniform random baseline (lower bound) +- heuristic_baseline_policy: stateless cheapest-viable routing +- debug_upper_bound_policy: oracle with internal state access (test only) +- always_route_a_policy: degenerate (always cheapest) +- always_route_b_policy: degenerate (always balanced fallback) +- always_route_c_policy: degenerate (always most reliable) +- always_shed_load_policy: degenerate (always shed) +""" + +from __future__ import annotations + +import random as stdlib_random +from typing import Optional + +from .models import Action, ActionType, InternalState, Observation +from .reward import BUDGET_WEIGHT + + +def random_policy(obs: Observation, rng: Optional[stdlib_random.Random] = None) -> Action: + """Uniform random over all 4 actions. No state awareness.""" + r = rng or stdlib_random.Random() + choice = r.choice(list(ActionType)) + return Action(action_type=choice) + + +def heuristic_baseline_policy(obs: Observation) -> Action: + """ + Stateless heuristic: prefer cheapest provider with status > threshold. + Fallback to next cheapest. shed_load only if ALL below threshold. + + Budget-aware: when budget is critically low, only use the cheapest + viable provider or shed load to avoid the -10 budget exhaustion penalty. + No privileged information. Uses only what the agent can observe. + """ + threshold = 0.52 + + # Providers ordered by cost (cheapest first): A, B, C + providers = [ + ("route_to_a", obs.provider_a_status), + ("route_to_b", obs.provider_b_status), + ("route_to_c", obs.provider_c_status), + ] + + # Budget safety: when critically low, exclude expensive providers + # to prevent the -10.0 terminal budget exhaustion penalty. + # Only blocks C ($0.10/req) when budget can't absorb it. + if obs.budget_remaining < 0.10: + # Only consider A ($0.01) and B ($0.05) — skip C + for action_name, status in providers[:2]: + if status > threshold or status == 0.5: + return Action(action_type=ActionType(action_name)) + return Action(action_type=ActionType.SHED_LOAD) + + for action_name, status in providers: + if status > threshold or status == 0.5: + return Action(action_type=ActionType(action_name)) + + # All providers below threshold → shed load + return Action(action_type=ActionType.SHED_LOAD) + + +def debug_upper_bound_policy(obs: Observation, internal_state: InternalState) -> Action: + """ + Oracle policy with access to true internal health values. + Used ONLY for debugging and validation — NOT a fair benchmark. + + Strategy: expected-value routing using true health, with hard budget + feasibility constraint. Routes to the cheapest provider whose health + is high enough, but won't pick an expensive provider if it would + exhaust the budget. + """ + initial_budget = internal_state.initial_budget_dollars + if initial_budget <= 0: + initial_budget = 1.0 + + budget_dollars = internal_state.budget_dollars + remaining_steps = max(1, internal_state.max_steps - internal_state.current_step) + + providers_info = [ + ("route_to_a", internal_state.providers["A"].current_health, + internal_state.providers["A"].cost_per_request), + ("route_to_b", internal_state.providers["B"].current_health, + internal_state.providers["B"].cost_per_request), + ("route_to_c", internal_state.providers["C"].current_health, + internal_state.providers["C"].cost_per_request), + ] + + best_action = None + best_ev = float("-inf") + + for action_name, health, cost in providers_info: + # Hard feasibility: can we afford this provider for remaining steps? + # If not, skip it entirely to avoid budget exhaustion penalty (-10) + if cost * remaining_steps > budget_dollars: + continue + + # Expected per-step reward matching reward.py: + # P(success) * 1.0 + P(fail) * -2.0 - (cost/initial_budget) * BUDGET_WEIGHT + ev = health * 1.0 + (1.0 - health) * (-2.0) - (cost / initial_budget) * BUDGET_WEIGHT + + if ev > best_ev: + best_ev = ev + best_action = action_name + + if best_action is None: + # No affordable provider — pick the cheapest one we can still afford once + for action_name, health, cost in providers_info: + if cost <= budget_dollars: + ev = health * 1.0 + (1.0 - health) * (-2.0) - (cost / initial_budget) * BUDGET_WEIGHT + if ev > best_ev: + best_ev = ev + best_action = action_name + + if best_action is None or best_ev < -0.5: + return Action(action_type=ActionType.SHED_LOAD) + + return Action(action_type=ActionType(best_action)) + + +def always_route_a_policy(obs: Observation) -> Action: + """Degenerate: always route to cheapest provider A.""" + return Action(action_type=ActionType.ROUTE_TO_A) + + +def always_route_b_policy(obs: Observation) -> Action: + """Degenerate: always route to balanced provider B.""" + return Action(action_type=ActionType.ROUTE_TO_B) + + +def always_route_c_policy(obs: Observation) -> Action: + """Degenerate: always route to most expensive/reliable provider C.""" + return Action(action_type=ActionType.ROUTE_TO_C) + + +def always_shed_load_policy(obs: Observation) -> Action: + """Degenerate: always shed load (never routes).""" + return Action(action_type=ActionType.SHED_LOAD) diff --git a/budget_router/reward.py b/budget_router/reward.py new file mode 100644 index 0000000000000000000000000000000000000000..603b3e8fde52f33da83f9f0869dd34d8d7fb991b --- /dev/null +++ b/budget_router/reward.py @@ -0,0 +1,281 @@ +""" +Reward computation for the Budget Router environment. + +Per-step reward (4 additive terms max) and episode-level grader metrics. +""" + +from __future__ import annotations + +import math +from typing import Any, Dict, List + + +BUDGET_WEIGHT = 5.0 # Scales cost penalty so it's meaningful vs success/failure signal + + +def step_reward( + action_type: str, + request_succeeded: bool, + provider_cost: float, + initial_budget: float, + latency_ms: float, + sla_ceiling_ms: float, +) -> float: + """ + Compute single-step reward. Maximum 4 additive terms. + + For shed_load: fixed penalty of -0.5 (replaces routing terms). + For routing actions: + +1.0 if request succeeded, -2.0 if failed + -(provider_cost / initial_budget) * BUDGET_WEIGHT as cost penalty + -(excess_latency / sla_ceiling_ms) if latency exceeds SLA + + Returns: + float: The step reward. Never returns NaN. + """ + # Safety: prevent NaN from division by zero + if initial_budget <= 0: + initial_budget = 1.0 + if sla_ceiling_ms <= 0: + sla_ceiling_ms = 1.0 + + # shed_load: flat penalty, no routing terms + if action_type == "shed_load": + return -0.5 + + reward = 0.0 + + # Term 1: Success / failure + if request_succeeded: + reward += 1.0 + else: + reward += -2.0 + + # Term 2: Cost penalty (always applied for routing actions) + cost_penalty = -(provider_cost / initial_budget) * BUDGET_WEIGHT + reward += cost_penalty + + # Term 3: Latency breach penalty + if latency_ms > sla_ceiling_ms: + excess = latency_ms - sla_ceiling_ms + latency_penalty = -(excess / sla_ceiling_ms) + reward += latency_penalty + + # Safety: NaN guard + if math.isnan(reward): + reward = -2.0 + + return reward + + +def episode_metrics(history: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Compute deterministic episode-level grader metrics. + + Args: + history: List of step info dicts from the episode. + + Returns: + Dict with grader metrics: + - total_reward + - success_rate + - total_cost_spent + - average_latency_ms + - sla_met (bool) + - queue_overflow_events (int) + """ + if not history: + return { + "total_reward": 0.0, + "success_rate": 0.0, + "total_cost_spent": 0.0, + "average_latency_ms": 0.0, + "sla_met": True, + "queue_overflow_events": 0, + } + + total_reward = sum(h.get("reward", 0.0) for h in history) + + # Only count routing steps (not shed_load) for success rate + routing_steps = [h for h in history if h.get("action_type") != "shed_load"] + if routing_steps: + successes = sum(1 for h in routing_steps if h.get("request_succeeded", False)) + success_rate = successes / len(routing_steps) + else: + success_rate = 0.0 + + total_cost = sum(h.get("cost", 0.0) for h in history) + + latencies = [h.get("latency_ms", 0.0) for h in routing_steps] + avg_latency = sum(latencies) / len(latencies) if latencies else 0.0 + + sla_ceiling = history[0].get("sla_ceiling_ms", 500.0) + sla_met = all(lat <= sla_ceiling for lat in latencies) if latencies else True + + queue_overflows = sum(1 for h in history if h.get("queue_overflow", False)) + + return { + "total_reward": round(total_reward, 4), + "success_rate": round(success_rate, 4), + "total_cost_spent": round(total_cost, 4), + "average_latency_ms": round(avg_latency, 2), + "sla_met": sla_met, + "queue_overflow_events": queue_overflows, + } + + +def grade_episode(history: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Compute episode-level grader score in [0, 1] with weighted breakdown. + + overall = 0.30 × success_score + + 0.20 × latency_score + + 0.15 × budget_score + + 0.15 × sla_score + + 0.20 × adaptation_score + + Component definitions: + success_score: Fraction of ALL episode steps with a successful routed request. + Denominator = total steps (not routed steps), so partial abstention is penalised. + latency_score: 1.0 - (avg_latency / sla_ceiling), clamped to [0, 1]. + budget_score: Fraction of initial budget NOT spent, clamped to [0, 1]. + sla_score: Fraction of routed requests with latency <= sla_ceiling. + adaptation_score: Post-degradation success rate — measures whether the + agent detected and adapted to provider degradation. + + Adaptation score window semantics by task: + - easy (no degradation): No post-degradation window exists. + adaptation_score = 1.0 (adaptation not required → full marks). + - medium (A degrades after step 5): Window = routing steps with + step > 5. Measures success rate after A begins failing. + - hard (A degrades from step 0): Window = routing steps with + step > 1 (one warm-up step allowed). Covers nearly all steps. + - hard_multi (A from step 0, B from step 10): Blended score: + 0.5 × primary_adaptation (steps between primary and secondary) + + 0.5 × secondary_adaptation (steps after secondary event). + + All component scores are clamped to [0.0, 1.0]. + + Args: + history: List of step info dicts from the episode. + + Returns: + Dict with 'overall_score' and per-component breakdown. + """ + # Note: step_reward() is shaped for learning signal (dense + budget cliff). + # grade_episode() is the semantic evaluation metric. Divergence is intentional. + if not history: + return { + "overall_score": 0.0, + "success_score": 0.0, + "latency_score": 0.0, + "budget_score": 0.0, + "sla_score": 0.0, + "adaptation_score": 0.0, + } + + metrics = episode_metrics(history) + + # success_score: fraction of ALL episode steps that resulted in a successful routed request. + # Denominator is total steps, not routed steps, so partial abstention is penalised. + # A policy that serves 10/20 and succeeds each time scores 0.50, not 1.0. + total_steps = len(history) + routing_steps = [h for h in history if h.get("action_type") != "shed_load"] + routed_successes = sum(1 for h in routing_steps if h.get("request_succeeded", False)) + success_score = routed_successes / total_steps if total_steps > 0 else 0.0 + + sla_ceiling_ms = float(history[0].get("sla_ceiling_ms", 500.0) or 500.0) + avg_latency_ms = float(metrics.get("average_latency_ms", 0.0)) + + if sla_ceiling_ms <= 0: + sla_ceiling_ms = 1.0 + + + # Fix 1: No routing attempts = no service delivered. Quality scores must reflect this. + if routing_steps: + latency_score = 1.0 - min(1.0, avg_latency_ms / sla_ceiling_ms) + sla_ok = sum(1 for h in routing_steps if float(h.get("latency_ms", 0.0)) <= sla_ceiling_ms) + sla_score = sla_ok / len(routing_steps) + else: + latency_score = 0.0 + sla_score = 0.0 + + # Budget score: penalize spending relative to initial budget, not theoretical max + total_cost = float(metrics.get("total_cost_spent", 0.0)) + initial_budget = float(history[0].get("initial_budget", 1.0) or 1.0) + budget_score = max(0.0, 1.0 - total_cost / initial_budget) + + # Adaptation score: measures post-degradation success rate. + # Directly measures whether the agent detected and adapted to degradation. + adaptation_score = 0.0 + _raw_degrade = history[0].get("degradation_start_step") + degradation_start = int(_raw_degrade) if _raw_degrade is not None else 999 + _raw_secondary = history[0].get("secondary_degradation_start_step") + secondary_start = int(_raw_secondary) if _raw_secondary is not None else None + + if degradation_start < 999: + if secondary_start is not None: + # Fix 2: hard_multi — blended adaptation across primary and secondary windows + primary_window = [h for h in routing_steps + if int(h.get("step", 0)) > max(degradation_start, 1) + and int(h.get("step", 0)) <= secondary_start] + secondary_window = [h for h in routing_steps + if int(h.get("step", 0)) > secondary_start] + + if primary_window: + primary_adaptation = sum(1 for h in primary_window if h.get("request_succeeded", False)) / len(primary_window) + else: + primary_adaptation = 0.0 + + if secondary_window: + secondary_adaptation = sum(1 for h in secondary_window if h.get("request_succeeded", False)) / len(secondary_window) + else: + secondary_adaptation = 0.0 + + if not primary_window and not secondary_window: + adaptation_score = 0.0 + else: + adaptation_score = 0.5 * primary_adaptation + 0.5 * secondary_adaptation + else: + # Single degradation event: existing logic unchanged + # Use max(degradation_start, 1) to ensure at least one warm-up step + # before post-degradation tracking, even when degradation_start=0 + post_degrade = [h for h in routing_steps + if int(h.get("step", 0)) > max(degradation_start, 1)] + if post_degrade: + post_successes = sum(1 for h in post_degrade if h.get("request_succeeded", False)) + adaptation_score = post_successes / len(post_degrade) + else: + # No degradation event. Award adaptation based on routing quality instead. + # A do-nothing (always shed_load) policy gets 0, not 1.0. + if routing_steps: + quality_successes = sum(1 for h in routing_steps if h.get("request_succeeded", False)) + adaptation_score = quality_successes / total_steps # total_steps denominator penalizes abstention + else: + adaptation_score = 0.0 + + overall = ( + 0.3 * success_score + + 0.2 * latency_score + + 0.15 * budget_score + + 0.15 * sla_score + + 0.2 * adaptation_score + ) + + # Hard penalty for budget exhaustion: incomplete episodes are not reliable systems. + # A policy that routes aggressively and goes bankrupt at step 17 should not outscore + # one that completes all 20 steps. 0.75x preserves partial credit for good routing + # before exhaustion, but makes budget-exhausted policies non-competitive. + episode_terminated_early = any(h.get('budget_exhausted', False) for h in history) + if episode_terminated_early: + overall = overall * 0.75 + + overall = max(0.0, min(1.0, overall)) + return { + "overall_score": round(overall, 4), + "success_score": round(max(0.0, min(1.0, success_score)), 4), + "latency_score": round(max(0.0, min(1.0, latency_score)), 4), + "budget_score": round(max(0.0, min(1.0, budget_score)), 4), + "sla_score": round(max(0.0, min(1.0, sla_score)), 4), + "adaptation_score": round(max(0.0, min(1.0, adaptation_score)), 4), + } diff --git a/budget_router/tasks.py b/budget_router/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..1a63fd3c5acec4b0695d2baba6398c74a9a090af --- /dev/null +++ b/budget_router/tasks.py @@ -0,0 +1,108 @@ +""" +Task preset configurations: EASY, MEDIUM, HARD. + +Each is a TaskConfig instance passed to reset(scenario=config). +""" + +from .models import TaskConfig + + +EASY = TaskConfig( + name="easy", + description="Stable providers. Cheapest is viable but not dominant. Smart routing wins.", + initial_budget=1.0, + cost_a=0.01, + cost_b=0.05, + cost_c=0.10, + reliability_a=0.76, # lowered so always-A isn't dominant; forces routing quality to matter + reliability_b=0.92, + reliability_c=0.99, + latency_a=100.0, + latency_b=150.0, + latency_c=200.0, + sla_ceiling_ms=500.0, + degradation_start_step=999, # effectively no degradation + degradation_rate=0.0, + degradation_target="A", + max_steps=20, + max_queue_backlog=10, + latency_noise_std=30.0, +) + + +MEDIUM = TaskConfig( + name="medium", + description="Provider A degrades sharply after step 5. Must adapt routing.", + initial_budget=0.95, + cost_a=0.01, + cost_b=0.05, + cost_c=0.10, + reliability_a=0.85, + reliability_b=0.92, + reliability_c=0.99, + latency_a=100.0, + latency_b=150.0, + latency_c=200.0, + sla_ceiling_ms=500.0, + degradation_start_step=5, + degradation_rate=0.15, # sharp drop after step 5 + degradation_target="A", + max_steps=20, + max_queue_backlog=10, + latency_noise_std=30.0, +) + + +HARD = TaskConfig( + name="hard", + description="Provider A degrades aggressively from step 0. Tight budget. High noise. Must diversify immediately.", + initial_budget=0.85, + cost_a=0.01, + cost_b=0.05, + cost_c=0.10, + reliability_a=0.85, + reliability_b=0.92, + reliability_c=0.99, + latency_a=100.0, + latency_b=150.0, + latency_c=200.0, + sla_ceiling_ms=500.0, + degradation_start_step=0, # degrades from the start + degradation_start_jitter=3, + degradation_rate=0.15, # faster than MEDIUM (was 0.08) + degradation_target="A", + max_steps=20, + max_queue_backlog=10, + latency_noise_std=50.0, # significantly more noise (was 40.0) +) + + +HARD_MULTI = TaskConfig( + name="hard_multi", + description="A degrades from step 0, B degrades from step 10. Multi-provider cascade. Slightly wider budget to reward efficient routing.", + initial_budget=1.10, + cost_a=0.01, + cost_b=0.05, + cost_c=0.10, + reliability_a=0.85, + reliability_b=0.92, + reliability_c=0.99, + latency_a=100.0, + latency_b=150.0, + latency_c=200.0, + sla_ceiling_ms=500.0, + degradation_start_step=0, + degradation_start_jitter=3, + degradation_rate=0.12, + degradation_target="A", + secondary_degradation_start_step=10, + secondary_degradation_start_jitter=3, + secondary_degradation_rate=0.10, + secondary_degradation_target="B", + max_steps=20, + max_queue_backlog=10, + latency_noise_std=50.0, +) + + +TASK_PRESETS = {"easy": EASY, "medium": MEDIUM, "hard": HARD, "hard_multi": HARD_MULTI} diff --git a/budget_router/tests/__init__.py b/budget_router/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..45a63a8efc86091c0c1e7a753f30a13e9fb1c114 --- /dev/null +++ b/budget_router/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the Budget Router environment - package init.""" diff --git a/budget_router/tests/test_environment.py b/budget_router/tests/test_environment.py new file mode 100644 index 0000000000000000000000000000000000000000..f68d62c0ee360afeaade4c553584fe20fbbc9aea --- /dev/null +++ b/budget_router/tests/test_environment.py @@ -0,0 +1,502 @@ +""" +Tests for the Budget Router environment core correctness and reward sanity. + +All tests from are implemented here. +""" + +import math +import random + +import pytest + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation +from budget_router.policies import ( + always_route_a_policy, + always_route_b_policy, + always_route_c_policy, + always_shed_load_policy, + heuristic_baseline_policy, + random_policy, +) +from budget_router.reward import step_reward +from budget_router.tasks import EASY, HARD, HARD_MULTI, MEDIUM + + +# ─── Helpers ──────────────────────────────────────────────────────────── + + +def run_full_episode(env, policy_fn, seed, scenario, policy_name=""): + """Run a full episode and return (observations, rewards, done_flag, steps).""" + obs = env.reset(seed=seed, scenario=scenario) + observations = [obs] + rewards = [] + steps = 0 + rng = random.Random(seed + 10000) if "random" in policy_name else None + + while not obs.done and steps < scenario.max_steps: + if "random" in policy_name: + action = policy_fn(obs, rng=rng) + else: + action = policy_fn(obs) + obs = env.step(action) + observations.append(obs) + rewards.append(obs.reward) + steps += 1 + + return observations, rewards, obs.done, steps + + +# ─── Core Correctness Tests ──────────────────────────────────────────── + + +class TestCoreCorrectness: + """Core environment correctness tests.""" + + def test_reset_returns_valid_observation(self): + """reset() returns Observation with ALL values in [0.0, 1.0].""" + env = BudgetRouterEnv() + obs = env.reset(seed=42, scenario=EASY) + + assert isinstance(obs, Observation) + assert 0.0 <= obs.provider_a_status <= 1.0 + assert 0.0 <= obs.provider_b_status <= 1.0 + assert 0.0 <= obs.provider_c_status <= 1.0 + assert 0.0 <= obs.budget_remaining <= 1.0 + assert 0.0 <= obs.queue_backlog <= 1.0 + assert 0.0 <= obs.system_latency <= 1.0 + assert 0.0 <= obs.step_count <= 1.0 + + def test_step_after_reset_no_crash(self): + """step() after reset() does not crash and returns valid types.""" + env = BudgetRouterEnv() + obs = env.reset(seed=42, scenario=EASY) + action = Action(action_type=ActionType.ROUTE_TO_A) + obs = env.step(action) + + assert isinstance(obs, Observation) + assert isinstance(obs.done, bool) + assert isinstance(obs.reward, (int, float)) + + def test_step_before_reset_no_crash(self): + """step() before reset() auto-initializes so the default OpenEnv web UI is safe.""" + env = BudgetRouterEnv() + action = Action(action_type=ActionType.ROUTE_TO_A) + obs = env.step(action) + + assert isinstance(obs, Observation) + assert isinstance(obs.done, bool) + assert isinstance(obs.reward, (int, float)) + + def test_episode_terminates_at_or_before_20(self): + """Episode terminates at or before step 20.""" + env = BudgetRouterEnv() + for scenario in [EASY, MEDIUM, HARD]: + obs = env.reset(seed=42, scenario=scenario) + steps = 0 + while not obs.done and steps < 25: # give extra margin to catch bugs + action = Action(action_type=ActionType.ROUTE_TO_B) + obs = env.step(action) + steps += 1 + assert steps <= 20, f"Episode ran {steps} steps on {scenario.name}" + + def test_deterministic_trajectories_same_seed(self): + """Two reset() calls with same seed produce identical full trajectories.""" + env = BudgetRouterEnv() + + # Run 1 + obs1_list, rewards1, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=42, scenario=MEDIUM + ) + + # Run 2 + obs2_list, rewards2, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=42, scenario=MEDIUM + ) + + assert len(rewards1) == len(rewards2) + for r1, r2 in zip(rewards1, rewards2): + assert r1 == r2, f"Rewards differ: {r1} vs {r2}" + + def test_budget_remaining_never_nan(self): + """budget_remaining never returns NaN.""" + env = BudgetRouterEnv() + for scenario in [EASY, MEDIUM, HARD]: + observations, _, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=42, scenario=scenario + ) + for obs in observations: + assert not math.isnan(obs.budget_remaining), "budget_remaining is NaN" + + def test_provider_status_in_bounds(self): + """All provider_status values stay in [0.0, 1.0] throughout episode.""" + env = BudgetRouterEnv() + for scenario in [EASY, MEDIUM, HARD]: + observations, _, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=0, scenario=scenario + ) + for obs in observations: + assert 0.0 <= obs.provider_a_status <= 1.0 + assert 0.0 <= obs.provider_b_status <= 1.0 + assert 0.0 <= obs.provider_c_status <= 1.0 + + def test_system_latency_not_always_zero(self): + """system_latency is NOT always 0.0 across a full episode (dead channel guard).""" + env = BudgetRouterEnv() + observations, _, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=42, scenario=MEDIUM + ) + # Skip first observation (from reset) — latency may be initial value + latencies = [obs.system_latency for obs in observations[1:]] + assert any(lat > 0.0 for lat in latencies), "system_latency is always 0.0 — dead channel" + + def test_all_observation_fields_in_range(self): + """All Observation fields remain within [0.0, 1.0] at every step.""" + env = BudgetRouterEnv() + for scenario in [EASY, MEDIUM, HARD]: + for seed in [0, 1, 2]: + observations, _, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=scenario + ) + for obs in observations: + assert 0.0 <= obs.provider_a_status <= 1.0 + assert 0.0 <= obs.provider_b_status <= 1.0 + assert 0.0 <= obs.provider_c_status <= 1.0 + assert 0.0 <= obs.budget_remaining <= 1.0 + assert 0.0 <= obs.queue_backlog <= 1.0 + assert 0.0 <= obs.system_latency <= 1.0 + assert 0.0 <= obs.step_count <= 1.0 + + +# ─── Reward Sanity Tests ─────────────────────────────────────────────── + + +class TestRewardSanity: + """Reward correctness tests.""" + + def test_shed_load_reward_less_than_successful_route_c(self): + """shed_load reward < successful route_to_c reward (holding all else equal).""" + shed_r = step_reward("shed_load", False, 0.0, 5.0, 0.0, 500.0) + route_c_r = step_reward("route_to_c", True, 0.10, 5.0, 200.0, 500.0) + assert shed_r < route_c_r, f"shed ({shed_r}) >= route_c success ({route_c_r})" + + def test_failed_route_less_than_successful_route(self): + """Failed route reward < successful route reward.""" + failed_r = step_reward("route_to_a", False, 0.01, 5.0, 300.0, 500.0) + success_r = step_reward("route_to_a", True, 0.01, 5.0, 100.0, 500.0) + assert failed_r < success_r, f"failed ({failed_r}) >= success ({success_r})" + + def test_route_a_cost_less_than_route_c_cost(self): + """route_to_a cost < route_to_c cost in info dict.""" + env = BudgetRouterEnv() + env.reset(seed=42, scenario=EASY) + + obs_a = env.step(Action(action_type=ActionType.ROUTE_TO_A)) + cost_a = obs_a.metadata.get("cost", 0) + + env.reset(seed=42, scenario=EASY) + obs_c = env.step(Action(action_type=ActionType.ROUTE_TO_C)) + cost_c = obs_c.metadata.get("cost", 0) + + assert cost_a < cost_c, f"cost_a ({cost_a}) >= cost_c ({cost_c})" + + def test_route_a_under_hard_degradation_lower_cumulative(self): + """route_to_a under hard degradation gets lower cumulative reward than route_to_c.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4] + + total_a = 0.0 + total_c = 0.0 + + for seed in seeds: + _, rewards_a, _, _ = run_full_episode( + env, always_route_a_policy, seed=seed, scenario=HARD + ) + total_a += sum(r or 0 for r in rewards_a) + + _, rewards_c, _, _ = run_full_episode( + env, always_route_c_policy, seed=seed, scenario=HARD + ) + total_c += sum(r or 0 for r in rewards_c) + + assert total_a < total_c, ( + f"always_route_a ({total_a:.2f}) >= always_route_c ({total_c:.2f}) on HARD" + ) + + +# ─── Degenerate Policy Sanity ────────────────────────────────────────── + + +class TestDegeneratePolicySanity: + """Degenerate policy tests.""" + + def test_always_route_a_does_not_dominate_baseline_medium(self): + """always_route_a does not dominate heuristic baseline on medium across dev seeds.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + baseline_rewards = [] + always_a_rewards = [] + + for seed in seeds: + _, rewards, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=MEDIUM + ) + baseline_rewards.append(sum(r or 0 for r in rewards)) + + _, rewards, _, _ = run_full_episode( + env, always_route_a_policy, seed=seed, scenario=MEDIUM + ) + always_a_rewards.append(sum(r or 0 for r in rewards)) + + baseline_mean = sum(baseline_rewards) / len(baseline_rewards) + always_a_mean = sum(always_a_rewards) / len(always_a_rewards) + + assert baseline_mean >= always_a_mean, ( + f"always_route_a ({always_a_mean:.2f}) dominates baseline ({baseline_mean:.2f}) on medium" + ) + + def test_always_route_c_does_not_dominate_baseline_overall(self): + """always_route_c does not dominate heuristic baseline across all tasks.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + baseline_total = 0.0 + always_c_total = 0.0 + + for scenario in [EASY, MEDIUM, HARD]: + for seed in seeds: + _, rewards, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=scenario + ) + baseline_total += sum(r or 0 for r in rewards) + + _, rewards, _, _ = run_full_episode( + env, always_route_c_policy, seed=seed, scenario=scenario + ) + always_c_total += sum(r or 0 for r in rewards) + + assert baseline_total >= always_c_total, ( + f"always_route_c ({always_c_total:.2f}) dominates baseline ({baseline_total:.2f}) overall" + ) + + def test_always_route_b_does_not_dominate_baseline_medium(self): + """always_route_b does not dominate heuristic baseline on medium across dev seeds.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + baseline_rewards = [] + always_b_rewards = [] + + for seed in seeds: + _, rewards, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=MEDIUM + ) + baseline_rewards.append(sum(r or 0 for r in rewards)) + + _, rewards, _, _ = run_full_episode( + env, always_route_b_policy, seed=seed, scenario=MEDIUM + ) + always_b_rewards.append(sum(r or 0 for r in rewards)) + + baseline_mean = sum(baseline_rewards) / len(baseline_rewards) + always_b_mean = sum(always_b_rewards) / len(always_b_rewards) + + assert baseline_mean >= always_b_mean, ( + f"always_route_b ({always_b_mean:.2f}) dominates baseline ({baseline_mean:.2f}) on medium" + ) + + def test_always_shed_load_worse_than_baseline_easy(self): + """always_shed_load performs materially worse than heuristic baseline on easy.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + baseline_rewards = [] + always_shed_rewards = [] + + for seed in seeds: + _, rewards, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=EASY + ) + baseline_rewards.append(sum(r or 0 for r in rewards)) + + _, rewards, _, _ = run_full_episode( + env, always_shed_load_policy, seed=seed, scenario=EASY + ) + always_shed_rewards.append(sum(r or 0 for r in rewards)) + + baseline_mean = sum(baseline_rewards) / len(baseline_rewards) + shed_mean = sum(always_shed_rewards) / len(always_shed_rewards) + + assert baseline_mean > shed_mean, ( + f"always_shed ({shed_mean:.2f}) >= baseline ({baseline_mean:.2f}) on easy" + ) + + +class TestBehavioralGuards: + """Behavioral regression tests for the repo's core adaptation claims.""" + + def test_heuristic_outperforms_always_route_a_on_hard_dev_seeds(self): + """On HARD, reactive routing must beat the cheapest non-adaptive baseline.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + heuristic_rewards = [] + always_a_rewards = [] + + for seed in seeds: + _, rewards, _, _ = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=HARD + ) + heuristic_rewards.append(sum(r or 0 for r in rewards)) + + _, rewards, _, _ = run_full_episode( + env, always_route_a_policy, seed=seed, scenario=HARD + ) + always_a_rewards.append(sum(r or 0 for r in rewards)) + + heuristic_mean = sum(heuristic_rewards) / len(heuristic_rewards) + always_a_mean = sum(always_a_rewards) / len(always_a_rewards) + + assert heuristic_mean > always_a_mean, ( + f"heuristic ({heuristic_mean:.2f}) must beat always_route_a " + f"({always_a_mean:.2f}) on hard dev seeds" + ) + + def test_heuristic_completes_hard_multi_without_budget_exhaustion(self): + """On HARD_MULTI dev seeds, the baseline should finish without budget bankruptcy.""" + env = BudgetRouterEnv() + seeds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + for seed in seeds: + _, _, done, steps = run_full_episode( + env, heuristic_baseline_policy, seed=seed, scenario=HARD_MULTI + ) + exhausted = any( + step.get("budget_exhausted", False) for step in env._internal.history + ) + + assert done, f"heuristic did not terminate on hard_multi seed={seed}" + assert steps == HARD_MULTI.max_steps, ( + f"heuristic ended after {steps} steps, expected {HARD_MULTI.max_steps} " + f"on hard_multi seed={seed}" + ) + assert not exhausted, ( + f"heuristic hit budget exhaustion on hard_multi seed={seed}" + ) + + +# ─── Grader Semantic Tests ────────────────────────────────────────────── + + +class TestGraderSemantics: + """Pin the exact grader semantics changed by the abstention and hard_multi fixes. + + These tests defend against regressions to grade_episode() — the most + judge-sensitive function in the repo. + """ + + def _make_step(self, step, action, succeeded, cost, latency, degrade=999, secondary=None): + return { + "step": step, "action_type": action, + "request_succeeded": succeeded, "cost": cost, + "latency_ms": latency, "reward": 0.9, + "sla_ceiling_ms": 500.0, "initial_budget": 1.0, + "degradation_start_step": degrade, + "secondary_degradation_start_step": secondary, + } + + def test_pure_abstention_scores_below_0_40_on_easy(self): + """A policy that sheds all load must score < 0.40 overall on easy. + + Before the fix this scored ~0.70 (sla=1.0, latency=1.0 on empty routing set). + """ + from budget_router.reward import grade_episode + + history = [ + self._make_step(i, "shed_load", False, 0.0, 0.0, degrade=999) + for i in range(1, 21) + ] + result = grade_episode(history) + + assert result["overall_score"] < 0.40, ( + f"Pure abstention scored {result['overall_score']} >= 0.40 on easy " + f"(grader exploit not fixed)" + ) + assert result["sla_score"] == 0.0, "sla_score should be 0.0 when no requests routed" + assert result["latency_score"] == 0.0, "latency_score should be 0.0 when no requests routed" + assert result["success_score"] == 0.0, "success_score should be 0.0 when no requests routed" + assert result["budget_score"] == 1.0, "budget_score should be 1.0 when nothing spent" + assert result["adaptation_score"] == 0.0, ( + "adaptation_score should be 0.0 on easy when the policy only sheds load" + ) + + def test_partial_abstention_scores_less_than_full_service(self): + """A policy that sheds 50% of load must score < a policy that serves all 20 steps. + + Before the success_score denominator fix, partial abstention could outscore + full service because budget_score rewarded not spending. + """ + from budget_router.reward import grade_episode + + # Mixed: 10 sheds then 10 successful routes + mixed = ( + [self._make_step(i, "shed_load", False, 0.0, 0.0) for i in range(1, 11)] + + [self._make_step(i, "route_to_a", True, 0.01, 110.0) for i in range(11, 21)] + ) + # Full service: 20 successful routes + full = [self._make_step(i, "route_to_a", True, 0.01, 110.0) for i in range(1, 21)] + + r_mixed = grade_episode(mixed) + r_full = grade_episode(full) + + assert r_mixed["overall_score"] < r_full["overall_score"], ( + f"Partial abstention ({r_mixed['overall_score']}) >= full service " + f"({r_full['overall_score']}) — grader still rewards low-throughput" + ) + assert r_mixed["success_score"] < r_full["success_score"], ( + f"success_score should be lower for 10/20 served ({r_mixed['success_score']}) " + f"than 20/20 served ({r_full['success_score']})" + ) + + def test_hard_multi_adaptation_uses_secondary_window(self): + """grade_episode computes blended adaptation for hard_multi (secondary window included). + + Verifies that secondary_degradation_start_step=10 in step_info causes + grade_episode to split the adaptation window at step 10 and blend 0.5/0.5. + """ + from budget_router.reward import grade_episode + + # Build a hard_multi episode: steps 1-10 primary window (route A, succeeds), + # steps 11-20 secondary window (route A, fails — B degraded, agent stuck) + history = [] + for i in range(1, 11): + history.append(self._make_step(i, "route_to_a", True, 0.01, 110.0, degrade=0, secondary=10)) + for i in range(11, 21): + history.append(self._make_step(i, "route_to_a", False, 0.01, 700.0, degrade=0, secondary=10)) + + result = grade_episode(history) + + # primary_window: steps > max(0,1)=1 and <= 10 → steps 2..10 → 9 steps, all succeed → 1.0 + # secondary_window: steps > 10 → steps 11..20 → 10 steps, all fail → 0.0 + # blended = 0.5 * 1.0 + 0.5 * 0.0 = 0.5 + expected_adaptation = 0.5 + assert abs(result["adaptation_score"] - expected_adaptation) < 0.01, ( + f"hard_multi blended adaptation expected ~{expected_adaptation}, " + f"got {result['adaptation_score']}" + ) + + # Compare with an equivalent hard (non-multi) episode to confirm they diverge + history_hard = [] + for i in range(1, 11): + history_hard.append(self._make_step(i, "route_to_a", True, 0.01, 110.0, degrade=0, secondary=None)) + for i in range(11, 21): + history_hard.append(self._make_step(i, "route_to_a", False, 0.01, 700.0, degrade=0, secondary=None)) + + result_hard = grade_episode(history_hard) + # hard (no secondary): post_degrade = steps > max(0,1)=1 → steps 2..20 → 19 steps + # 9 succeed (steps 2-10), 10 fail (steps 11-20) → 9/19 ≈ 0.473 + assert result["adaptation_score"] != result_hard["adaptation_score"], ( + f"hard_multi and hard got identical adaptation_score={result['adaptation_score']} " + f"— secondary window not being used" + ) diff --git a/budget_router/tests/test_eval_all_seed_selection.py b/budget_router/tests/test_eval_all_seed_selection.py new file mode 100644 index 0000000000000000000000000000000000000000..77672edb964b27208b87570d1f006412a04a98fe --- /dev/null +++ b/budget_router/tests/test_eval_all_seed_selection.py @@ -0,0 +1,41 @@ +import importlib.util +from pathlib import Path + +import pytest + + +def _load_eval_all(): + path = Path(__file__).resolve().parents[2] / "eval" / "eval_all.py" + spec = importlib.util.spec_from_file_location("eval_all", path) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def test_seed_values_override_named_seed_set(): + eval_all = _load_eval_all() + + assert eval_all.select_seeds( + seed_set="dev", + seeds=3, + seed_values="200,201,202", + ) == [200, 201, 202] + + +def test_seed_values_accept_commas_and_whitespace(): + eval_all = _load_eval_all() + + assert eval_all.select_seeds( + seed_set="heldout", + seeds=1, + seed_values="200, 201 202", + ) == [200, 201, 202] + + +def test_seed_values_reject_empty_input(): + eval_all = _load_eval_all() + + with pytest.raises(ValueError, match="No explicit seeds"): + eval_all.select_seeds(seed_set="dev", seeds=3, seed_values=" , ") + diff --git a/budget_router/tests/test_grpo_training_reward.py b/budget_router/tests/test_grpo_training_reward.py new file mode 100644 index 0000000000000000000000000000000000000000..bf9699860bc4feab86db402e930c28ee3b4925c2 --- /dev/null +++ b/budget_router/tests/test_grpo_training_reward.py @@ -0,0 +1,154 @@ +import pytest + +# GRPO tests import train/learn_experiment.py, which loads torch, datasets, peft, +# transformers, trl at module import. Those live under `--extra grpo` (torch alone +# may exist via `--extra training`, which is not enough). +for _grpo_mod in ("torch", "datasets", "peft", "transformers", "trl"): + pytest.importorskip(_grpo_mod) + +from budget_router.reward import grade_episode +from train.grpo_env import BudgetRouterGRPOEnv +from train.learn_experiment import build_dataset, build_system_prompt, reward_func, summarize_training_rollout + + +def _step_once(env: BudgetRouterGRPOEnv) -> None: + # Any routing action is fine; we just need non-empty history. + # Use B as a reasonably stable default. + try: + env.route_to_b() + except ValueError as e: + # If an episode somehow terminates early, that's fine for the test harness, + # but it would make the "partial episode" test invalid. + raise AssertionError(f"Episode ended unexpectedly after one step: {e}") from e + + +def _run_to_completion(env: BudgetRouterGRPOEnv) -> None: + # Drive the episode until the GRPO wrapper signals completion. + while True: + try: + env.route_to_b() + except ValueError: + return + + +def test_reward_func_empty_history_returns_zero(): + env = BudgetRouterGRPOEnv() + env.reset(scenario="hard_multi", seed=0) + + rewards = reward_func([env]) + assert rewards == [0.0] + + +def test_reward_func_partial_episode_is_progress_scaled_not_full_grader(): + env = BudgetRouterGRPOEnv() + env.reset(scenario="hard_multi", seed=0) + + _step_once(env) + + internal = env._env._internal + assert internal.history, "test precondition: history must be non-empty" + assert not internal.episode_done, "test precondition: episode must be incomplete" + + grader = float(grade_episode(internal.history)["overall_score"]) + progress = internal.current_step / max(1, internal.max_steps) + expected = grader * progress + + # This is the critical regression guard: training reward must not be equal + # to the raw grader when the episode is incomplete. + rewards = reward_func([env]) + assert rewards == [pytest.approx(expected, abs=1e-6)] + assert rewards[0] != pytest.approx(grader, abs=1e-6) + + +def test_reward_func_complete_episode_equals_full_grader(): + env = BudgetRouterGRPOEnv() + env.reset(scenario="hard_multi", seed=0) + + _run_to_completion(env) + + internal = env._env._internal + assert internal.history, "test precondition: history must be non-empty" + assert internal.episode_done, "test precondition: episode must be complete" + + grader = float(grade_episode(internal.history)["overall_score"]) + rewards = reward_func([env]) + assert rewards == [pytest.approx(grader, abs=1e-6)] + + +def test_training_rollout_summary_exposes_partial_episode_health(): + env = BudgetRouterGRPOEnv() + env.reset(scenario="hard_multi", seed=0) + + _step_once(env) + _step_once(env) + + summary = summarize_training_rollout([env]) + + assert summary["env_steps_mean"] == pytest.approx(2.0) + assert summary["env_steps_min"] == 2 + assert summary["env_steps_max"] == 2 + assert summary["episode_completion_rate"] == 0.0 + assert summary["progress_mean"] == pytest.approx(0.1) + assert summary["raw_grader_mean"] > summary["training_reward_mean"] + + +def test_training_rollout_summary_exposes_action_sequence_diversity(): + same_a = BudgetRouterGRPOEnv() + same_b = BudgetRouterGRPOEnv() + different = BudgetRouterGRPOEnv() + for env in (same_a, same_b, different): + env.reset(scenario="hard_multi", seed=0) + + same_a.route_to_b() + same_a.route_to_b() + same_b.route_to_b() + same_b.route_to_b() + different.route_to_a() + different.route_to_a() + + summary = summarize_training_rollout([same_a, same_b, different]) + + assert summary["action_sequences"] == [ + "route_to_b route_to_b", + "route_to_b route_to_b", + "route_to_a route_to_a", + ] + assert summary["unique_action_sequences"] == 2 + assert summary["action_sequence_counts"] == { + "route_to_b route_to_b": 2, + "route_to_a route_to_a": 1, + } + + +def test_grpo_tool_feedback_is_compact_for_multi_turn_budget(): + env = BudgetRouterGRPOEnv() + env.reset(scenario="hard_multi", seed=0) + + feedback = env.route_to_b() + + assert len(feedback) < 180 + assert "steps_left=" in feedback + assert "health=" in feedback + + +def test_explore_prompt_preserves_tool_format_without_deterministic_policy(): + prompt = build_system_prompt("explore") + + assert "" in prompt + assert '"name": "route_to_a"' in prompt + assert "route_to_a" in prompt + assert "route_to_b" in prompt + assert "route_to_c" in prompt + assert "shed_load" in prompt + assert "0.52" not in prompt + assert "cheapest healthy provider" not in prompt.lower() + assert "Observation:" not in prompt + assert "route_to_a route_to_b route_to_c" not in prompt + + +def test_build_dataset_uses_requested_prompt_style(): + dataset = build_dataset(n=1, prompt_style="explore") + system_prompt = dataset[0]["prompt"][0]["content"] + + assert system_prompt == build_system_prompt("explore") + diff --git a/budget_router/tests/test_inference_prompt.py b/budget_router/tests/test_inference_prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..da045a9a14f976d06a9ddb33a905234ed4774786 --- /dev/null +++ b/budget_router/tests/test_inference_prompt.py @@ -0,0 +1,94 @@ +from inference import SYSTEM_PROMPT +from budget_router.models import Observation +from inference import LLMRouter + + +def test_system_prompt_has_required_structural_sections(): + upper_prompt = SYSTEM_PROMPT.upper() + assert "GOLDEN RULE" in upper_prompt or "DEFAULT STRATEGY" in upper_prompt + assert "BUDGET RUNWAY" in upper_prompt + assert "TASK PROFILE" in upper_prompt + assert "NOISE CALIBRATION" in upper_prompt + + +def test_system_prompt_communicates_bankruptcy_consequence(): + assert "-10" in SYSTEM_PROMPT or "bankruptcy" in SYSTEM_PROMPT.lower() + assert "0.500" in SYSTEM_PROMPT or "unobserved" in SYSTEM_PROMPT.lower() + + +class _FakeResponse: + def __init__(self, content: str) -> None: + self.choices = [type("Choice", (), {"message": type("Message", (), {"content": content})()})()] + + +class _FakeClient: + def with_options(self, **kwargs): + return self + + @property + def chat(self): + return self + + @property + def completions(self): + return self + + def create(self, **kwargs): + return _FakeResponse("route_to_a") + + +def test_llm_router_preserves_task_name_on_first_step(): + router = LLMRouter(api_base_url="https://example.com/v1", model_name="test-model", api_key="test-key") + router._client = _FakeClient() + router.reset(task_name="hard_multi") + + obs = Observation( + provider_a_status=0.5, + provider_b_status=0.5, + provider_c_status=0.5, + budget_remaining=1.0, + queue_backlog=0.0, + system_latency=0.2, + step_count=0.0, + ) + + router.choose_action(obs) + + assert router._task_name == "hard_multi" + assert "task: hard_multi" in router._messages[-2]["content"] + + +def test_objective_feedback_mode_includes_previous_step_feedback(): + router = LLMRouter( + api_base_url="https://example.com/v1", + model_name="test-model", + api_key="test-key", + prompt_mode="objective_feedback", + ) + router._client = _FakeClient() + router.reset(task_name="hard_multi") + + obs = Observation( + provider_a_status=0.4, + provider_b_status=0.7, + provider_c_status=0.9, + budget_remaining=0.8, + queue_backlog=0.1, + system_latency=0.4, + step_count=0.5, + reward=-2.05, + metadata={ + "action_type": "route_to_a", + "request_succeeded": False, + "cost": 0.01, + "latency_ms": 620.0, + }, + ) + + router.choose_action(obs) + + prompt = router._messages[-2]["content"] + assert "previous_step_feedback:" in prompt + assert "previous_action: route_to_a" in prompt + assert "previous_reward: -2.05" in prompt + assert "previous_success: false" in prompt diff --git a/budget_router/tests/test_trace_episode.py b/budget_router/tests/test_trace_episode.py new file mode 100644 index 0000000000000000000000000000000000000000..8ce579c2d73883458c9b39141a7a674bf5292d08 --- /dev/null +++ b/budget_router/tests/test_trace_episode.py @@ -0,0 +1,43 @@ +import importlib.util +from pathlib import Path + + +def _load_trace_episode(): + path = Path(__file__).resolve().parents[2] / "eval" / "trace_episode.py" + spec = importlib.util.spec_from_file_location("trace_episode", path) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def test_trace_episode_returns_step_rows_and_scores_for_heuristic(): + trace_episode = _load_trace_episode() + + result = trace_episode.trace_episode(task_name="hard_multi", seed=3, policy_name="heuristic") + + assert result["task"] == "hard_multi" + assert result["seed"] == 3 + assert result["policy"] == "heuristic" + assert result["steps"] + assert len(result["steps"]) == result["episode_length"] + assert result["total_reward"] == round(sum(step["reward"] for step in result["steps"]), 4) + assert 0.0 <= result["grader"]["overall_score"] <= 1.0 + assert {"success_rate", "total_cost_spent", "average_latency_ms"}.issubset(result["metrics"]) + assert { + "provider_a_status", + "provider_b_status", + "provider_c_status", + "observed_budget_remaining", + }.issubset(result["steps"][0]) + + +def test_trace_episode_rejects_unknown_policy(): + trace_episode = _load_trace_episode() + + try: + trace_episode.trace_episode(task_name="hard_multi", seed=3, policy_name="unknown") + except ValueError as exc: + assert "Unknown policy" in str(exc) + else: + raise AssertionError("unknown policy should raise ValueError") diff --git a/budget_router/tests/test_validation.py b/budget_router/tests/test_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..2361a167c51a8e72d0ec314bd8c6d1a5dda75032 --- /dev/null +++ b/budget_router/tests/test_validation.py @@ -0,0 +1,140 @@ +""" +Tests for the validation harness. + +Covers: policy ordering, solvability, NaN safety, baseline stability, +and hard task crash resistance. +""" + +import math +import random + +import pytest + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.policies import ( + always_route_a_policy, + always_route_b_policy, + always_route_c_policy, + always_shed_load_policy, + debug_upper_bound_policy, + heuristic_baseline_policy, + random_policy, +) +from budget_router.tasks import EASY, HARD, MEDIUM +from budget_router.validation import DEVELOPMENT_SEEDS, HELDOUT_SEEDS, run_episode + + +# ─── Helpers ──────────────────────────────────────────────────────────── + + +def mean_reward_over_seeds(policy_fn, scenario, seeds, policy_name=""): + """Compute mean total reward for a policy across seeds.""" + env = BudgetRouterEnv() + rewards = [] + for seed in seeds: + metrics = run_episode(env, policy_fn, seed, scenario, policy_name=policy_name) + rewards.append(metrics["total_reward"]) + return sum(rewards) / len(rewards), rewards + + +# ─── Validation Tests ────────────────────────────────────────────────── + + +class TestValidation: + """Validation-level tests.""" + + def test_baseline_beats_random_easy_dev(self): + """Baseline beats random on easy task across development seeds.""" + baseline_mean, _ = mean_reward_over_seeds( + heuristic_baseline_policy, EASY, DEVELOPMENT_SEEDS + ) + random_mean, _ = mean_reward_over_seeds( + random_policy, EASY, DEVELOPMENT_SEEDS, policy_name="random" + ) + assert baseline_mean > random_mean, ( + f"baseline ({baseline_mean:.2f}) <= random ({random_mean:.2f}) on easy" + ) + + def test_upper_bound_beats_baseline_easy_dev(self): + """Upper bound beats or matches baseline on easy task across dev seeds.""" + baseline_mean, _ = mean_reward_over_seeds( + heuristic_baseline_policy, EASY, DEVELOPMENT_SEEDS + ) + ub_mean, _ = mean_reward_over_seeds( + debug_upper_bound_policy, EASY, DEVELOPMENT_SEEDS, policy_name="upper_bound" + ) + assert ub_mean >= baseline_mean, ( + f"oracle ({ub_mean:.2f}) < baseline ({baseline_mean:.2f}) on easy" + ) + + def test_easy_solvable_positive_reward(self): + """Easy task is solvable: baseline achieves positive total reward on seed=42.""" + env = BudgetRouterEnv() + metrics = run_episode(env, heuristic_baseline_policy, seed=42, scenario=EASY) + assert metrics["total_reward"] > 0, ( + f"baseline achieves {metrics['total_reward']:.2f} on easy/seed=42" + ) + + def test_hard_no_crash_dev_seeds(self): + """Hard task terminates without environment crash on development_seeds.""" + env = BudgetRouterEnv() + for seed in DEVELOPMENT_SEEDS: + try: + metrics = run_episode( + env, heuristic_baseline_policy, seed=seed, scenario=HARD + ) + assert metrics["episode_length"] <= 20 + except Exception as e: + pytest.fail(f"Hard task crashed on seed {seed}: {e}") + + def test_no_nan_rewards_all_combos(self): + """No reward is NaN across all (task, policy, seed_set) combinations.""" + env = BudgetRouterEnv() + policies = { + "random": random_policy, + "heuristic_baseline": heuristic_baseline_policy, + "upper_bound": debug_upper_bound_policy, + "always_route_a": always_route_a_policy, + "always_route_b": always_route_b_policy, + "always_route_c": always_route_c_policy, + "always_shed_load": always_shed_load_policy, + } + + for scenario in [EASY, MEDIUM, HARD]: + for policy_name, policy_fn in policies.items(): + for seed in DEVELOPMENT_SEEDS[:3]: # subset for speed + metrics = run_episode( + env, policy_fn, seed, scenario, policy_name=policy_name + ) + assert not math.isnan(metrics["total_reward"]), ( + f"NaN reward: {scenario.name}/{policy_name}/seed={seed}" + ) + + def test_baseline_stability_heldout(self): + """Baseline remains within reasonable stability margin on heldout seeds.""" + for scenario in [EASY, MEDIUM, HARD]: + dev_mean, _ = mean_reward_over_seeds( + heuristic_baseline_policy, scenario, DEVELOPMENT_SEEDS + ) + heldout_mean, _ = mean_reward_over_seeds( + heuristic_baseline_policy, scenario, HELDOUT_SEEDS + ) + margin = max(2.0, 0.40 * abs(dev_mean)) + assert abs(heldout_mean - dev_mean) <= margin, ( + f"Baseline unstable on {scenario.name}: " + f"dev={dev_mean:.2f}, heldout={heldout_mean:.2f}, margin={margin:.2f}" + ) + + def test_baseline_beats_always_route_b_dev(self): + """Baseline beats always_route_b on all tasks across development seeds.""" + for scenario in [EASY, MEDIUM, HARD]: + baseline_mean, _ = mean_reward_over_seeds( + heuristic_baseline_policy, scenario, DEVELOPMENT_SEEDS + ) + always_b_mean, _ = mean_reward_over_seeds( + always_route_b_policy, scenario, DEVELOPMENT_SEEDS + ) + assert baseline_mean >= always_b_mean, ( + f"baseline ({baseline_mean:.2f}) < always_route_b ({always_b_mean:.2f}) on {scenario.name}" + ) diff --git a/budget_router/validation.py b/budget_router/validation.py new file mode 100644 index 0000000000000000000000000000000000000000..2e156f973bd6875864e35bffbd488c41c3809ecc --- /dev/null +++ b/budget_router/validation.py @@ -0,0 +1,424 @@ +""" +Validation harness for the Budget Router environment. + +- run_validation(): runs all policies across all tasks and seed sets +- run_manual_trace(): step-by-step debug trace +- assert_all_checks(): hard assertions that must pass before submission +- print_results_table(): formatted results display +""" + +from __future__ import annotations + +import math +import random +from typing import Any, Callable, Dict, List, Optional, Tuple + +from .environment import BudgetRouterEnv +from .models import Action, ActionType, InternalState, Observation, TaskConfig +from .policies import ( + always_route_a_policy, + always_route_b_policy, + always_route_c_policy, + always_shed_load_policy, + debug_upper_bound_policy, + heuristic_baseline_policy, + random_policy, +) +from .reward import episode_metrics +from .tasks import EASY, HARD, HARD_MULTI, MEDIUM, TASK_PRESETS + +# ─── Seed sets ────────────────────────────────────────────────────────── + +DEVELOPMENT_SEEDS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +HELDOUT_SEEDS = [100, 101, 102, 103, 104] + + +# ─── Episode runner ───────────────────────────────────────────────────── + + +def run_episode( + env: BudgetRouterEnv, + policy_fn: Callable, + seed: int, + scenario: TaskConfig, + policy_name: str = "", +) -> Dict[str, Any]: + """Run a single episode and return metrics.""" + obs = env.reset(seed=seed, scenario=scenario) + + # For random policy, seed a separate RNG + policy_rng = random.Random(seed + 10000) if "random" in policy_name else None + + total_reward = 0.0 + steps = 0 + + while not obs.done and steps < scenario.max_steps: + # Select action based on policy + if "upper_bound" in policy_name: + action = policy_fn(obs, env._internal) + elif "random" in policy_name: + action = policy_fn(obs, rng=policy_rng) + else: + action = policy_fn(obs) + + obs = env.step(action) + total_reward += (obs.reward or 0.0) + steps += 1 + + metrics = episode_metrics(env._internal.history) + metrics["total_reward"] = round(total_reward, 4) + metrics["episode_length"] = steps + + return metrics + + +# ─── Validation runner ────────────────────────────────────────────────── + + +def run_validation(seed_set_name: str = "development") -> Dict[str, Dict[str, Dict[str, Any]]]: + """ + Run all 6 policies on all 3 tasks for the given seed set. + + Returns: + Nested dict: results[task_name][policy_name] = { + "mean_reward", "std_reward", "min_reward", "max_reward", + "success_rate", "average_cost", "average_latency", + "all_rewards", "all_budgets", "all_lengths" + } + """ + seeds = DEVELOPMENT_SEEDS if seed_set_name == "development" else HELDOUT_SEEDS + + policies = { + "random": random_policy, + "heuristic_baseline": heuristic_baseline_policy, + "upper_bound": debug_upper_bound_policy, + "always_route_a": always_route_a_policy, + "always_route_b": always_route_b_policy, + "always_route_c": always_route_c_policy, + "always_shed_load": always_shed_load_policy, + } + + tasks = {"easy": EASY, "medium": MEDIUM, "hard": HARD, "hard_multi": HARD_MULTI} + results: Dict[str, Dict[str, Dict[str, Any]]] = {} + + env = BudgetRouterEnv() + + for task_name, task_config in tasks.items(): + results[task_name] = {} + for policy_name, policy_fn in policies.items(): + all_rewards = [] + all_success_rates = [] + all_costs = [] + all_latencies = [] + all_lengths = [] + + for seed in seeds: + metrics = run_episode( + env, policy_fn, seed, task_config, policy_name=policy_name + ) + all_rewards.append(metrics["total_reward"]) + all_success_rates.append(metrics["success_rate"]) + all_costs.append(metrics["total_cost_spent"]) + all_latencies.append(metrics["average_latency_ms"]) + all_lengths.append(metrics["episode_length"]) + + mean_r = sum(all_rewards) / len(all_rewards) + std_r = ( + sum((r - mean_r) ** 2 for r in all_rewards) / len(all_rewards) + ) ** 0.5 + + results[task_name][policy_name] = { + "mean_reward": round(mean_r, 4), + "std_reward": round(std_r, 4), + "min_reward": round(min(all_rewards), 4), + "max_reward": round(max(all_rewards), 4), + "success_rate": round( + sum(all_success_rates) / len(all_success_rates), 4 + ), + "average_cost": round(sum(all_costs) / len(all_costs), 4), + "average_latency": round( + sum(all_latencies) / len(all_latencies), 2 + ), + "all_rewards": all_rewards, + "all_lengths": all_lengths, + } + + return results + + +# ─── Results printer ──────────────────────────────────────────────────── + + +def print_results_table(results: Dict, seed_set_name: str = "development") -> None: + """Print formatted results table.""" + print(f"\n{'='*90}") + print(f" VALIDATION RESULTS — {seed_set_name.upper()} SEEDS") + print(f"{'='*90}") + + for task_name, policies in results.items(): + print(f"\n Task: {task_name.upper()}") + print(f" {'Policy':<20} {'Mean':>8} {'Std':>8} {'Min':>8} {'Max':>8} {'SucRate':>8} {'Cost':>8} {'Lat(ms)':>8}") + print(f" {'-'*76}") + for policy_name, stats in policies.items(): + print( + f" {policy_name:<20} " + f"{stats['mean_reward']:>8.2f} " + f"{stats['std_reward']:>8.2f} " + f"{stats['min_reward']:>8.2f} " + f"{stats['max_reward']:>8.2f} " + f"{stats['success_rate']:>8.2f} " + f"{stats['average_cost']:>8.4f} " + f"{stats['average_latency']:>8.1f}" + ) + + print(f"\n{'='*90}") + + +# ─── Manual Trace ────────────────────────────────────────────────────── + + +def run_manual_trace( + seed: int = 42, + scenario_name: str = "medium", + policy_fn: Optional[Callable] = None, + policy_name: str = "heuristic_baseline", +) -> None: + """ + Run a single episode with step-by-step trace in raw internal units. + PRIMARY debugging tool. + """ + scenario = TASK_PRESETS[scenario_name] + policy = policy_fn or heuristic_baseline_policy + env = BudgetRouterEnv() + + obs = env.reset(seed=seed, scenario=scenario) + policy_rng = random.Random(seed + 10000) + + print(f"\n{'─'*95}") + print(f" MANUAL TRACE — Scenario: {scenario_name.upper()}, Seed: {seed}, Policy: {policy_name}") + print(f"{'─'*95}") + print( + f" {'Step':>4} | {'Action':<10} | {'A_health':>8} | {'B_health':>8} | {'C_health':>8} | " + f"{'Latency':>8} | {'Budget$':>8} | {'Reward':>7} | {'Cumul':>7}" + ) + print(f" {'─'*91}") + + cumulative = 0.0 + steps = 0 + + while not obs.done and steps < scenario.max_steps: + if "upper_bound" in policy_name: + action = policy(obs, env._internal) + elif "random" in policy_name: + action = policy(obs, rng=policy_rng) + else: + action = policy(obs) + + obs = env.step(action) + steps += 1 + + reward = obs.reward or 0.0 + cumulative += reward + + # Read raw internal state for trace + s = env._internal + a_health = s.providers["A"].current_health + b_health = s.providers["B"].current_health + c_health = s.providers["C"].current_health + latency_ms = s.last_latency_ms + budget = s.budget_dollars + + print( + f" {steps:>4} | {action.action_type.value:<10} | " + f"{a_health:>8.3f} | {b_health:>8.3f} | {c_health:>8.3f} | " + f"{latency_ms:>6.0f}ms | ${budget:>7.2f} | " + f"{reward:>+7.2f} | {cumulative:>+7.2f}" + ) + + print(f" {'─'*91}") + + metrics = episode_metrics(env._internal.history) + print( + f" EPISODE END | " + f"success_rate={metrics['success_rate']:.2f} | " + f"total_cost=${metrics['total_cost_spent']:.4f} | " + f"sla_met={metrics['sla_met']} | " + f"total_reward={cumulative:.2f}" + ) + print(f"{'─'*95}\n") + + +# ─── Hard Assertions ─────────────────────────────────────────────────── + + +def assert_all_checks( + dev_results: Dict[str, Dict[str, Dict[str, Any]]], + heldout_results: Dict[str, Dict[str, Dict[str, Any]]], +) -> None: + """ + Run all hard assertions. All must pass before submission. + If any fails, fix the environment — do not weaken the assertion. + """ + print("\n" + "=" * 60) + print(" RUNNING HARD ASSERTION CHECKS") + print("=" * 60) + + passed = 0 + failed = 0 + total = 0 + + def check(condition: bool, msg: str) -> None: + nonlocal passed, failed, total + total += 1 + if condition: + passed += 1 + print(f" ✅ PASS: {msg}") + else: + failed += 1 + print(f" ❌ FAIL: {msg}") + + # ── Policy ordering (BOTH seed sets, ALL tasks) ── + # Note: hard_multi baseline > random only required on dev seeds — + # heldout random can occasionally beat the deterministic heuristic on hard_multi + for seed_set_name, results in [("dev", dev_results), ("heldout", heldout_results)]: + for task in ["easy", "medium", "hard"]: + baseline_mean = results[task]["heuristic_baseline"]["mean_reward"] + random_mean = results[task]["random"]["mean_reward"] + upper_bound_mean = results[task]["upper_bound"]["mean_reward"] + + check( + baseline_mean > random_mean, + f"[{seed_set_name}/{task}] baseline ({baseline_mean:.2f}) > random ({random_mean:.2f})", + ) + check( + upper_bound_mean >= baseline_mean, + f"[{seed_set_name}/{task}] oracle ({upper_bound_mean:.2f}) >= baseline ({baseline_mean:.2f})", + ) + # hard_multi: only check oracle >= baseline (heuristic fails by design) + hm_baseline = results["hard_multi"]["heuristic_baseline"]["mean_reward"] + hm_oracle = results["hard_multi"]["upper_bound"]["mean_reward"] + check( + hm_oracle >= hm_baseline, + f"[{seed_set_name}/hard_multi] oracle ({hm_oracle:.2f}) >= baseline ({hm_baseline:.2f})", + ) + + # ── Non-triviality ── + found_nontrivial = False + for task in ["easy", "medium", "hard", "hard_multi"]: + baseline_mean = dev_results[task]["heuristic_baseline"]["mean_reward"] + random_mean = dev_results[task]["random"]["mean_reward"] + if abs(random_mean) > 0: + gap = (baseline_mean - random_mean) / abs(random_mean) + else: + gap = abs(baseline_mean - random_mean) + if gap > 0.20: + found_nontrivial = True + break + check(found_nontrivial, "At least one task has >20% gap between baseline and random") + + # ── Solvability ── + easy_ub_reward = dev_results["easy"]["upper_bound"]["mean_reward"] + easy_ub_sr = dev_results["easy"]["upper_bound"]["success_rate"] + check(easy_ub_reward > 0, f"Oracle positive reward on easy ({easy_ub_reward:.2f})") + check(easy_ub_sr > 0.5, f"Oracle success rate on easy ({easy_ub_sr:.2f}) > 0.5") + + # ── Anti-gaming checks (hard_multi excluded — heuristic fails by design) ── + for task in ["easy", "medium", "hard"]: + baseline_mean = dev_results[task]["heuristic_baseline"]["mean_reward"] + always_a_mean = dev_results[task]["always_route_a"]["mean_reward"] + always_b_mean = dev_results[task]["always_route_b"]["mean_reward"] + always_shed_mean = dev_results[task]["always_shed_load"]["mean_reward"] + + check( + baseline_mean >= always_a_mean, + f"[dev/{task}] baseline ({baseline_mean:.2f}) >= always_a ({always_a_mean:.2f})", + ) + check( + baseline_mean >= always_b_mean, + f"[dev/{task}] baseline ({baseline_mean:.2f}) >= always_b ({always_b_mean:.2f})", + ) + check( + baseline_mean >= always_shed_mean, + f"[dev/{task}] baseline ({baseline_mean:.2f}) >= always_shed ({always_shed_mean:.2f})", + ) + + # Check that NOT all degenerate policies dominate baseline + for task in ["easy", "medium", "hard", "hard_multi"]: + baseline_mean = dev_results[task]["heuristic_baseline"]["mean_reward"] + always_a = dev_results[task]["always_route_a"]["mean_reward"] + always_b = dev_results[task]["always_route_b"]["mean_reward"] + always_c = dev_results[task]["always_route_c"]["mean_reward"] + always_shed = dev_results[task]["always_shed_load"]["mean_reward"] + check( + not ( + always_a >= baseline_mean + and always_b >= baseline_mean + and always_c >= baseline_mean + and always_shed >= baseline_mean + ), + f"[dev/{task}] heuristic provides strategic advantage over degenerate policies", + ) + + # ── Held-out robustness ── + for task in ["easy", "medium", "hard", "hard_multi"]: + baseline_dev = dev_results[task]["heuristic_baseline"]["mean_reward"] + baseline_heldout = heldout_results[task]["heuristic_baseline"]["mean_reward"] + margin = max(2.0, 0.40 * abs(baseline_dev)) + check( + abs(baseline_heldout - baseline_dev) <= margin, + f"[{task}] baseline stable: dev={baseline_dev:.2f}, heldout={baseline_heldout:.2f}, margin={margin:.2f}", + ) + + # ── Safety: NaN, budget explosion, infinite loops ── + all_rewards = [] + all_lengths = [] + for seed_set_name, results in [("dev", dev_results), ("heldout", heldout_results)]: + for task in ["easy", "medium", "hard"]: + for policy_name, stats in results[task].items(): + all_rewards.extend(stats["all_rewards"]) + all_lengths.extend(stats["all_lengths"]) + + check( + all(not math.isnan(r) for r in all_rewards), + f"No NaN rewards across {len(all_rewards)} episodes", + ) + check( + all(ep_len <= 20 for ep_len in all_lengths), + f"No episode exceeds 20 steps (max seen: {max(all_lengths) if all_lengths else 0})", + ) + + # ── Summary ── + print(f"\n{'='*60}") + print(f" RESULTS: {passed}/{total} passed, {failed}/{total} failed") + print(f"{'='*60}") + + if failed > 0: + print(f"\n ⚠️ {failed} assertion(s) FAILED. Fix the environment before submission.") + else: + print(f"\n 🎉 All assertions passed! Environment is ready for submission.") + + +# ─── Main entry point ────────────────────────────────────────────────── + + +def main() -> None: + """Run full validation suite.""" + # Run both seed sets + print("Running validation on DEVELOPMENT seeds...") + dev_results = run_validation("development") + print_results_table(dev_results, "development") + + print("\nRunning validation on HELD-OUT seeds...") + heldout_results = run_validation("heldout") + print_results_table(heldout_results, "heldout") + + # Manual trace + run_manual_trace(seed=42, scenario_name="medium") + run_manual_trace(seed=42, scenario_name="hard_multi") + + # Hard assertions + assert_all_checks(dev_results, heldout_results) + + +if __name__ == "__main__": + main() diff --git a/check_leak.py b/check_leak.py new file mode 100644 index 0000000000000000000000000000000000000000..3795e33572981ef62d8598ae71d26bd5a9d9ba55 --- /dev/null +++ b/check_leak.py @@ -0,0 +1,181 @@ +""" +check_leak.py — Validates BudgetRouterGRPOEnv before GRPO training. + +Checks: + 1. Tool methods return strings (not crash). + 2. Episode ends gracefully via ValueError (TRL-idiomatic done signal). + 3. Reward is a float in [0, 1] — not a dict, not NaN. + 4. History uses actual_degradation_start (jittered) — NOT the config constant. + This proves grade_episode() will compute correct adaptation windows. + 5. 10-step reward trajectory printed: verify no explosion/vanishing. + 6. Provider status IS present in tool responses (intentional — text interface needs it). + +Run: + uv run python check_leak.py +""" + +import sys + + +def main() -> None: + try: + from train.grpo_env import BudgetRouterGRPOEnv + from budget_router.reward import grade_episode + from budget_router.tasks import HARD_MULTI + except ImportError as e: + print(f"[FAIL] Import error: {e}") + sys.exit(1) + + print("=" * 60) + print("BudgetRouterGRPOEnv — Pre-training Validation") + print("=" * 60) + + # ── Check 0: transformers version (soft warning — required for environment_factory) ── + print("\n[CHECK 0] transformers version (required for environment_factory)...") + try: + import transformers + ver_str = transformers.__version__ + # TRL's environment_factory requires transformers >= 4.47.0 (confirmed shipping in + # stable builds as of Apr 2026). Exact minimum threshold is version-specific to TRL. + # If not installed, training will fail at import time — caught here early. + print(f" ✅ transformers=={ver_str} installed.") + # Soft check: warn if below 4.47 (minimum known to ship environment_factory support) + major, minor = int(ver_str.split(".")[0]), int(ver_str.split(".")[1]) + if major < 4 or (major == 4 and minor < 47): + print( + f" ⚠️ WARNING: transformers {ver_str} may be too old for environment_factory.\n" + f" Recommended: pip install 'transformers>=4.47.0' or install from main." + ) + except ImportError: + print( + " ⚠️ WARNING: transformers is NOT installed in this venv.\n" + " Install before GRPO training: pip install 'transformers>=4.47.0' trl accelerate peft" + ) + + # ── Check 1: reset() returns a non-empty string ───────────────────── + print("\n[CHECK 1] reset() returns rich text observation...") + + env = BudgetRouterGRPOEnv() + obs_text = env.reset(scenario="hard_multi", seed=42) + assert isinstance(obs_text, str) and len(obs_text) > 10, \ + f"reset() should return non-empty string, got: {obs_text!r}" + assert "Budget" in obs_text, "reset() should mention Budget" + assert "Provider" in obs_text, "reset() should include provider status (text interface, not sanitized)" + print(f" ✅ reset() returned {len(obs_text)} chars. Provider status PRESENT (correct for text interface).") + print(f" Preview: {obs_text[:120].replace(chr(10), ' ')}...") + + # ── Check 2: Tool methods return strings step-by-step ─────────────── + print("\n[CHECK 2] Tool methods return strings and accumulate history...") + env2 = BudgetRouterGRPOEnv() + env2.reset(scenario="hard_multi", seed=42) + + step_results = [] + episode_done = False + for step in range(25): # more than max_steps to test guard + action_fn = [env2.route_to_a, env2.route_to_b, env2.shed_load, env2.route_to_b][step % 4] + try: + result = action_fn() + assert isinstance(result, str), f"Tool method should return str, got {type(result)}" + step_results.append(result) + print(f" Step {step + 1:02d}: ✅ {result[:80].replace(chr(10), ' ')}...") + except ValueError as e: + episode_done = True + print(f" Step {step + 1:02d}: ✅ Episode ended via ValueError (TRL-idiomatic): {str(e)[:80]}...") + break + + assert episode_done, "Episode should end with ValueError before step 25" + assert len(step_results) > 0, "At least one tool step should complete" + print(f" ✅ Episode ended correctly after {len(step_results)} tool calls.") + + # ── Check 3: Reward is float in [0, 1] ────────────────────────────── + print("\n[CHECK 3] Reward is float in [0, 1]...") + assert isinstance(env2.reward, float), \ + f"env.reward should be float, got {type(env2.reward)}: {env2.reward!r}" + assert 0.0 <= env2.reward <= 1.0, \ + f"env.reward should be in [0, 1], got {env2.reward}" + import math + assert not math.isnan(env2.reward), "env.reward is NaN — grade_episode bug" + print(f" ✅ env.reward = {env2.reward:.4f} (float, in [0,1], not NaN)") + + # ── Check 4: History uses actual jittered degradation_start_step ──── + print("\n[CHECK 4] History contains jittered actual_degradation_start (not config constant)...") + history = env2._env._internal.history + assert len(history) > 0, "History should not be empty after episode" + + # Read degradation_start_step from step_info (written by environment.py) + step_info_degrade_start = history[0].get("degradation_start_step") + # Read the actual jittered value from internal state + actual_jittered_start = env2._env._internal.actual_degradation_start + # Config constant for hard_multi + config_constant = HARD_MULTI.degradation_start_step # = 0 + + print(f" Config constant (degradation_start_step): {config_constant}") + print(f" step_info[degradation_start_step]: {step_info_degrade_start}") + print(f" internal.actual_degradation_start: {actual_jittered_start}") + + assert step_info_degrade_start is not None, \ + "step_info missing degradation_start_step — grade_episode() will break" + assert step_info_degrade_start == actual_jittered_start, \ + (f"step_info uses wrong degradation onset! " + f"Got {step_info_degrade_start}, expected {actual_jittered_start}. " + f"This would corrupt adaptation scores in grade_episode().") + print(f" ✅ Jittered onset correctly propagated through step_info.") + + # ── Check 5: grade_episode() on history returns consistent score ───── + print("\n[CHECK 5] grade_episode(history) matches env.reward...") + grader_result = grade_episode(history) + assert isinstance(grader_result, dict), "grade_episode should return dict" + grader_score = float(grader_result["overall_score"]) + assert abs(grader_score - env2.reward) < 1e-6, \ + f"env.reward ({env2.reward}) != grade_episode score ({grader_score}). Mismatch." + print(f" ✅ grade_episode overall_score = {grader_score:.4f}, env.reward = {env2.reward:.4f}. Match confirmed.") + + # ── Check 6: 10-episode reward trajectory ──────────────────────────── + print("\n[CHECK 6] 10-episode reward trajectory (hard_multi, varying seeds)...") + print(" Episode | Seed | Steps | Score | Reward-in-range") + rewards = [] + for ep, seed in enumerate(range(10)): + env3 = BudgetRouterGRPOEnv() + env3.reset(scenario="hard_multi", seed=seed) + done = False + steps = 0 + while not done and steps < 30: + # Alternate actions: A, B, A, B... (simple test policy) + action_fn = env3.route_to_a if steps % 2 == 0 else env3.route_to_b + try: + action_fn() + steps += 1 + except ValueError: + done = True + reward = env3.reward + rewards.append(reward) + in_range = "✅" if 0.0 <= reward <= 1.0 else "❌" + print(f" Ep {ep+1:02d} | {seed:4d} | {steps:5d} | {reward:.4f} | {in_range}") + + import statistics + if len(rewards) > 1: + std = statistics.stdev(rewards) + mean = statistics.mean(rewards) + print(f"\n Mean reward: {mean:.4f} | Std: {std:.4f}") + if std < 0.03: + print( + f" ⚠️ WARNING: Low reward variance (std={std:.4f}). GRPO may get weak gradient signal.\n" + f" Mitigation: Use num_generations=8, hard_multi scenario, and a small LLM\n" + f" at initialization that makes diverse routing decisions." + ) + else: + print(f" ✅ Reward variance is sufficient for GRPO learning (std={std:.4f} > 0.03).") + + print("\n" + "=" * 60) + print("✅ ALL CHECKS PASSED — BudgetRouterGRPOEnv is ready for GRPO training.") + print("=" * 60) + print("\nRecommended training config (Mac MPS / Colab):") + print(" scenario: hard_multi") + print(" num_generations: 8") + print(" model: Qwen2.5-1.5B (Mac 16GB) / Qwen2.5-7B (Colab T4)") + print(" Mac: TRL + PyTorch MPS (set PYTORCH_ENABLE_MPS_FALLBACK=1)") + print(" Colab: Unsloth + vLLM on NVIDIA T4/A100") + + +if __name__ == "__main__": + main() diff --git a/client.py b/client.py new file mode 100644 index 0000000000000000000000000000000000000000..cf925ff1fc1b196ea2168ea4ff83135ff40bb0e1 --- /dev/null +++ b/client.py @@ -0,0 +1,3 @@ +from budget_router.client import BudgetRouterClient + +__all__ = ["BudgetRouterClient"] diff --git a/eval/eval_all.py b/eval/eval_all.py new file mode 100644 index 0000000000000000000000000000000000000000..156174f8bb41a65bc1c202cf6431020e3c1ff440 --- /dev/null +++ b/eval/eval_all.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +""" +eval_all.py — Budget Router Consolidated Evaluator +==================================================== +Runs heuristic + LLM (+ optional PPO) across all tasks and seeds. +Outputs a Markdown table + per-episode JSON to outputs/. + +Usage: + # Quick (3 seeds, heuristic + LLM): + uv run python eval_all.py + + # Full (10 seeds, all policies): + uv run python eval_all.py --seeds 10 --policies heuristic llm + + # Heuristic only (no API needed): + uv run python eval_all.py --policies heuristic + + # Specific tasks: + uv run python eval_all.py --tasks hard hard_multi --seeds 5 + + # Explicit fresh seed bucket: + uv run python eval_all.py --tasks hard_multi --seed-values "200,201,202" + +Prerequisites: + export HF_TOKEN= # required for LLM policy + export API_BASE_URL=https://router.huggingface.co/v1 # default + export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct # default + +Output: + outputs/eval_results_.json — full per-episode data + outputs/eval_summary_.md — markdown table for README +""" + +import json +import os +import sys +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional + +import typer + +# ── Add parent to path so we can import budget_router ────────────────────── +sys.path.insert(0, str(Path(__file__).parent)) + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation, TaskConfig +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import episode_metrics, grade_episode +from budget_router.tasks import EASY, HARD, HARD_MULTI, MEDIUM + +from inference import LLMRouter + +# ── Config ────────────────────────────────────────────────────────────────── + +TASKS: Dict[str, TaskConfig] = { + "easy": EASY, + "medium": MEDIUM, + "hard": HARD, + "hard_multi": HARD_MULTI, +} + +SEED_SETS: Dict[str, List[int]] = { + "dev": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "heldout": [100, 101, 102, 103, 104, 105, 106, 107, 108, 109], +} + +API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN") +API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1") +MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct") +LLM_LOG_RAW = (os.getenv("LLM_LOG_RAW") or "").strip().lower() in {"1", "true", "yes", "y", "on"} +LLM_LOG_RAW_MAX_CHARS = int(os.getenv("LLM_LOG_RAW_MAX_CHARS") or "220") + + +def select_seeds(seed_set: str, seeds: int, seed_values: Optional[str] = None) -> List[int]: + """Resolve either a named seed set or an explicit comma/space-separated seed list.""" + if seed_values is not None: + parsed = [int(part) for part in seed_values.replace(",", " ").split()] + if not parsed: + raise ValueError("No explicit seeds provided in --seed-values") + return parsed + + if seed_set not in SEED_SETS: + raise ValueError(f"Unknown seed set: {seed_set}. Choose from: {list(SEED_SETS)}") + + named_seeds = SEED_SETS[seed_set] + return named_seeds[:max(1, min(seeds, len(named_seeds)))] + + +def _single_line(value: str | None) -> str: + if not value: + return "null" + return str(value).replace("\n", " ").replace("\r", " ") + + +def _truncate(value: str | None, max_chars: int) -> str: + s = _single_line(value).strip() + if len(s) <= max_chars: + return s + return s[: max(0, max_chars - 3)] + "..." + + +# ── Policies ──────────────────────────────────────────────────────────────── +def _llm_choose_action(policy: LLMRouter, obs: Observation) -> str: + action = policy.choose_action(obs) + return action.action_type.value + + +def _heuristic(obs: Observation) -> str: + return heuristic_baseline_policy(obs).action_type.value + + +# ── Episode runner ─────────────────────────────────────────────────────────── + +def run_one_episode( + task_name: str, + task_cfg: TaskConfig, + seed: int, + policy_name: str, + policy, # callable or LLMPolicy +) -> Dict: + env = BudgetRouterEnv() + if policy_name == "llm": + policy.reset(task_name=task_name) + + obs = env.reset(seed=seed, scenario=task_cfg) + rewards = [] + actions = [] + + while not obs.done: + if policy_name == "heuristic": + action_str = _heuristic(obs) + else: + action_str = _llm_choose_action(policy, obs) + + obs = env.step(Action(action_type=ActionType(action_str))) + reward = float(obs.reward or 0.0) + rewards.append(reward) + actions.append(action_str) + if policy_name == "llm" and LLM_LOG_RAW: + llm_raw = getattr(policy, "last_raw_output", None) + llm_parsed = getattr(policy, "last_parsed_action", None) + typer.echo( + f"[LLM] step={env._internal.current_step} action={action_str} " + f"reward={reward:+.2f} llm_raw={_truncate(llm_raw, max(20, LLM_LOG_RAW_MAX_CHARS))} " + f"llm_parsed={_single_line(llm_parsed)}" + ) + + grader = grade_episode(env._internal.history) + metrics = episode_metrics(env._internal.history) + + return { + "task": task_name, + "seed": seed, + "policy": policy_name, + "total_reward": round(sum(rewards), 4), + "grader_score": round(grader["overall_score"], 4), + "success_score": round(grader["success_score"], 4), + "budget_score": round(grader["budget_score"], 4), + "adaptation_score": round(grader["adaptation_score"], 4), + "latency_score": round(grader["latency_score"], 4), + "sla_score": round(grader["sla_score"], 4), + "success_rate": round(metrics["success_rate"], 4), + "steps": len(rewards), + "actions": actions, + "rewards": rewards, + } + + +# ── Summary helpers ────────────────────────────────────────────────────────── + +def _mean(vals: List[float]) -> float: + return round(sum(vals) / len(vals), 4) if vals else 0.0 + + +def build_summary(results: List[Dict]) -> Dict: + summary = {} + for r in results: + key = (r["task"], r["policy"]) + summary.setdefault(key, []).append(r) + return { + f"{task}|{pol}": { + "grader_mean": _mean([e["grader_score"] for e in eps]), + "reward_mean": _mean([e["total_reward"] for e in eps]), + "success_rate": _mean([e["success_rate"] for e in eps]), + "adaptation": _mean([e["adaptation_score"] for e in eps]), + "n": len(eps), + } + for (task, pol), eps in summary.items() + } + + +def render_markdown_table(summary: Dict, policies: List[str], tasks: List[str]) -> str: + task_labels = {"easy": "Easy", "medium": "Medium", "hard": "Hard", "hard_multi": "Hard_Multi"} + pol_headers = " | ".join(f"{p.upper()} Grader" for p in policies) + lines = [ + f"| Task | {pol_headers} | Notes |", + "|" + "---|" * (len(policies) + 2), + ] + for task in tasks: + scores = [] + for p in policies: + key = f"{task}|{p}" + s = summary.get(key, {}) + if s: + n = s["n"] + scores.append(f"{s['grader_mean']:.4f} (n={n})") + else: + scores.append("—") + note = "" + if task == "hard_multi" and len(policies) >= 2: + k0 = f"{task}|{policies[0]}" + k1 = f"{task}|{policies[1]}" + if k0 in summary and k1 in summary: + diff = summary[k1]["grader_mean"] - summary[k0]["grader_mean"] + if diff > 0: + note = f"LLM +{diff*100:.1f} points vs heuristic" + line = f"| {task_labels.get(task, task)} | {' | '.join(scores)} | {note} |" + lines.append(line) + return "\n".join(lines) + + +# ── CLI ────────────────────────────────────────────────────────────────────── + +app = typer.Typer(add_completion=False) + + +@app.command() +def main( + policies: List[str] = typer.Option(["heuristic", "llm"], help="Policies to run"), + tasks: List[str] = typer.Option(["easy", "medium", "hard", "hard_multi"], help="Tasks"), + seeds: int = typer.Option(3, help="Number of dev seeds (1-10, costs scale with LLM)"), + seed_set: str = typer.Option("dev", help="Seed set: dev | heldout"), + seed_values: Optional[str] = typer.Option(None, help="Explicit comma/space-separated seeds; overrides --seed-set/--seeds"), + out_dir: Path = typer.Option(Path("outputs"), help="Output directory"), +) -> None: + """Run Budget Router evaluation across policies, tasks, and seeds.""" + out_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + + try: + selected_seeds = select_seeds(seed_set=seed_set, seeds=seeds, seed_values=seed_values) + except ValueError as e: + typer.echo(str(e), err=True) + raise typer.Exit(1) from e + selected_tasks = {t: TASKS[t] for t in tasks if t in TASKS} + + if not selected_tasks: + typer.echo(f"No valid tasks. Choose from: {list(TASKS)}", err=True) + raise typer.Exit(1) + + # Build policy instances + policy_instances = {} + for p in policies: + if p == "heuristic": + policy_instances["heuristic"] = None # uses _heuristic() directly + elif p == "llm": + try: + if not API_KEY: + raise RuntimeError("No API key found. Set HF_TOKEN or API_KEY env var.") + policy_instances["llm"] = LLMRouter( + api_base_url=API_BASE_URL, model_name=MODEL_NAME, api_key=API_KEY + ) + typer.echo(f"LLM policy: {MODEL_NAME} via {API_BASE_URL}") + except RuntimeError as e: + typer.echo(f"[WARN] LLM policy unavailable: {e} — skipping", err=True) + elif p == "ppo": + typer.echo("[WARN] PPO eval not yet wired in this script — run your train_ppo.py separately", err=True) + + all_results = [] + total_episodes = len(policy_instances) * len(selected_tasks) * len(selected_seeds) + done = 0 + + for pol_name, pol_obj in policy_instances.items(): + for task_name, task_cfg in selected_tasks.items(): + for seed in selected_seeds: + typer.echo(f"[{done+1}/{total_episodes}] {pol_name:10s} | {task_name:12s} | seed={seed} ...", nl=False) + try: + result = run_one_episode(task_name, task_cfg, seed, pol_name, pol_obj) + all_results.append(result) + typer.echo(f" grader={result['grader_score']:.4f} reward={result['total_reward']:+.2f}") + except Exception as e: + typer.echo(f" ERROR: {e}", err=True) + done += 1 + + if not all_results: + typer.echo("No results produced.", err=True) + raise typer.Exit(1) + + # Save JSON + json_path = out_dir / f"eval_results_{ts}.json" + summary = build_summary(all_results) + output = {"metadata": {"timestamp": ts, "policies": policies, "tasks": tasks, "seeds": selected_seeds}, "summary": summary, "episodes": all_results} + json_path.write_text(json.dumps(output, indent=2)) + typer.echo(f"\nResults saved to {json_path}") + + # Save markdown table + md_table = render_markdown_table(summary, list(policy_instances.keys()), list(selected_tasks.keys())) + md_path = out_dir / f"eval_summary_{ts}.md" + md_path.write_text(f"# Budget Router Evaluation — {ts}\n\n{md_table}\n") + typer.echo(f"Markdown table saved to {md_path}") + typer.echo(f"\n{md_table}") + + +if __name__ == "__main__": + app() \ No newline at end of file diff --git a/eval/eval_all.sh b/eval/eval_all.sh new file mode 100755 index 0000000000000000000000000000000000000000..939ecdecdcb1469dcdbb6c7100565fee354e585c --- /dev/null +++ b/eval/eval_all.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# eval_all.sh — Budget Router Evaluator Wrapper +# ============================================== +# Runs heuristic + LLM eval and saves results to outputs/. +# +# Usage: +# chmod +x eval_all.sh +# ./eval_all.sh # quick: 3 seeds, heuristic + LLM +# ./eval_all.sh --seeds 10 # full dev set +# ./eval_all.sh --policies heuristic # no LLM (no API needed) +# ./eval_all.sh --tasks hard hard_multi --seeds 5 +# +# Prerequisites: +# export HF_TOKEN= +# export API_BASE_URL=https://router.huggingface.co/v1 (default) +# export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct (default) +# uv or pip install -e . (to install budget_router package) +# +# Outputs (in outputs/ directory): +# eval_results_.json — full per-episode grader breakdown +# eval_summary_.md — markdown table ready for README + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# ── Defaults ──────────────────────────────────────────────────────────────── +SEEDS=3 +POLICIES="heuristic llm" +TASKS="easy medium hard hard_multi" +SEED_SET="dev" +OUT_DIR="$REPO_ROOT/outputs" +EXTRA_ARGS=() + +# ── Parse CLI args ────────────────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case "$1" in + --seeds) SEEDS="$2"; shift 2 ;; + --seed-set) SEED_SET="$2"; shift 2 ;; + --out-dir) OUT_DIR="$2"; shift 2 ;; + --policies) + POLICIES="" + shift + while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do + POLICIES="$POLICIES $1"; shift + done + ;; + --tasks) + TASKS="" + shift + while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do + TASKS="$TASKS $1"; shift + done + ;; + *) EXTRA_ARGS+=("$1"); shift ;; + esac +done + +# ── Validate environment ───────────────────────────────────────────────────── +echo "" +echo "╔══════════════════════════════════════════════╗" +echo "║ Budget Router Evaluator ║" +echo "╚══════════════════════════════════════════════╝" +echo "" +echo "Config:" +echo " Policies: $POLICIES" +echo " Tasks: $TASKS" +echo " Seeds: $SEEDS (seed_set=$SEED_SET)" +echo " Output: $OUT_DIR/" +echo "" + +# Check HF_TOKEN if LLM in policies +if echo "$POLICIES" | grep -q "llm"; then + if [[ -z "${HF_TOKEN:-}" && -z "${API_KEY:-}" ]]; then + echo "⚠️ WARNING: HF_TOKEN and API_KEY not set." + echo " LLM policy will be skipped. Set HF_TOKEN to enable." + echo "" + else + TOKEN_PREVIEW="${HF_TOKEN:-${API_KEY:-}}" + echo " API key: ${TOKEN_PREVIEW:0:8}... (${#TOKEN_PREVIEW} chars)" + echo " Model: ${MODEL_NAME:-Qwen/Qwen2.5-72B-Instruct}" + echo " Endpoint: ${API_BASE_URL:-https://router.huggingface.co/v1}" + echo "" + fi +fi + +# ── Build typer args ───────────────────────────────────────────────────────── +TYPER_ARGS=( + "--seeds" "$SEEDS" + "--seed-set" "$SEED_SET" + "--out-dir" "$OUT_DIR" +) + +for p in $POLICIES; do + TYPER_ARGS+=("--policies" "$p") +done + +for t in $TASKS; do + TYPER_ARGS+=("--tasks" "$t") +done + +# ── Run ────────────────────────────────────────────────────────────────────── +cd "$SCRIPT_DIR" + +if command -v uv &>/dev/null; then + uv run python eval_all.py "${TYPER_ARGS[@]}" "${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}" +elif command -v python3 &>/dev/null; then + python3 eval_all.py "${TYPER_ARGS[@]}" "${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}" +else + echo "Error: neither uv nor python3 found." >&2 + exit 1 +fi + +echo "" +echo "✅ Evaluation complete. Results in $OUT_DIR/" \ No newline at end of file diff --git a/eval/outputs/prompt_audit/belief_v1_dev10/eval_results_20260425_160429.json b/eval/outputs/prompt_audit/belief_v1_dev10/eval_results_20260425_160429.json new file mode 100644 index 0000000000000000000000000000000000000000..246edab8b7c442da30690a035ebf0fd174c1feee --- /dev/null +++ b/eval/outputs/prompt_audit/belief_v1_dev10/eval_results_20260425_160429.json @@ -0,0 +1,1188 @@ +{ + "metadata": { + "timestamp": "20260425_160429", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6078, + "reward_mean": -2.9709, + "success_rate": 0.6998, + "adaptation": 0.6907, + "n": 10 + }, + "hard_multi|llm": { + "grader_mean": 0.6218, + "reward_mean": 1.3455, + "success_rate": 0.8535, + "adaptation": 0.8635, + "n": 10 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4659, + "grader_score": 0.5569, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.6032, + "latency_score": 0.4686, + "sla_score": 0.9474, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.3750364951788474, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -2.7727, + "grader_score": 0.6077, + "success_score": 0.7, + "budget_score": 0.0455, + "adaptation_score": 0.6833, + "latency_score": 0.5213, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.0, + "grader_score": 0.6165, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.6357, + "latency_score": 0.4967, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "heuristic", + "total_reward": -1.9895, + "grader_score": 0.6289, + "success_score": 0.7, + "budget_score": 0.2091, + "adaptation_score": 0.6833, + "latency_score": 0.5416, + "sla_score": 0.95, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.262190038025986, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "heuristic", + "total_reward": -4.0909, + "grader_score": 0.5933, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.6625, + "latency_score": 0.5175, + "sla_score": 1.0, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "heuristic", + "total_reward": -1.4024, + "grader_score": 0.607, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.8125, + "latency_score": 0.5142, + "sla_score": 0.9412, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.311463428136077, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "heuristic", + "total_reward": -3.7273, + "grader_score": 0.6546, + "success_score": 0.65, + "budget_score": 0.4545, + "adaptation_score": 0.6458, + "latency_score": 0.5611, + "sla_score": 1.0, + "success_rate": 0.65, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "heuristic", + "total_reward": 0.1818, + "grader_score": 0.6477, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.85, + "latency_score": 0.5613, + "sla_score": 1.0, + "success_rate": 0.7778, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "heuristic", + "total_reward": -8.3509, + "grader_score": 0.5338, + "success_score": 0.6, + "budget_score": 0.2, + "adaptation_score": 0.5682, + "latency_score": 0.4135, + "sla_score": 0.85, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.1359667972034475, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.4320645744998868, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2828540896362535, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "heuristic", + "total_reward": -1.0909, + "grader_score": 0.6315, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.7625, + "latency_score": 0.5336, + "sla_score": 1.0, + "success_rate": 0.7368, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": -3.7273, + "grader_score": 0.5176, + "success_score": 0.8333, + "budget_score": 0.0, + "adaptation_score": 0.7946, + "latency_score": 0.656, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": 6.1364, + "grader_score": 0.6994, + "success_score": 0.8, + "budget_score": 0.0273, + "adaptation_score": 0.8786, + "latency_score": 0.648, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": -1.5455, + "grader_score": 0.5204, + "success_score": 0.85, + "budget_score": 0.0, + "adaptation_score": 0.8071, + "latency_score": 0.6372, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "llm", + "total_reward": 9.0455, + "grader_score": 0.7388, + "success_score": 0.9, + "budget_score": 0.0091, + "adaptation_score": 0.8944, + "latency_score": 0.6926, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "llm", + "total_reward": -9.6364, + "grader_score": 0.4732, + "success_score": 0.7222, + "budget_score": 0.0, + "adaptation_score": 0.7083, + "latency_score": 0.6132, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "llm", + "total_reward": 1.9091, + "grader_score": 0.6665, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.9375, + "latency_score": 0.6085, + "sla_score": 1.0, + "success_rate": 0.8667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "llm", + "total_reward": 9.3182, + "grader_score": 0.7535, + "success_score": 0.9, + "budget_score": 0.0636, + "adaptation_score": 0.9444, + "latency_score": 0.6755, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "llm", + "total_reward": 3.1818, + "grader_score": 0.673, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.9375, + "latency_score": 0.6004, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "llm", + "total_reward": 3.3636, + "grader_score": 0.6573, + "success_score": 0.7, + "budget_score": 0.0727, + "adaptation_score": 0.8661, + "latency_score": 0.5661, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "llm", + "total_reward": -4.5909, + "grader_score": 0.5185, + "success_score": 0.8235, + "budget_score": 0.0, + "adaptation_score": 0.8667, + "latency_score": 0.605, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/prompt_audit/belief_v1_dev10/eval_summary_20260425_160429.md b/eval/outputs/prompt_audit/belief_v1_dev10/eval_summary_20260425_160429.md new file mode 100644 index 0000000000000000000000000000000000000000..6c32a6cf9a8e31d4f9718aa9109be938eca303db --- /dev/null +++ b/eval/outputs/prompt_audit/belief_v1_dev10/eval_summary_20260425_160429.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_160429 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6078 (n=10) | 0.6218 (n=10) | LLM +1.4 points vs heuristic | diff --git a/eval/outputs/prompt_audit/belief_v1_heldout5/eval_results_20260425_160016.json b/eval/outputs/prompt_audit/belief_v1_heldout5/eval_results_20260425_160016.json new file mode 100644 index 0000000000000000000000000000000000000000..4f3f8710704757c27ac4292c1c11d072de5273cd --- /dev/null +++ b/eval/outputs/prompt_audit/belief_v1_heldout5/eval_results_20260425_160016.json @@ -0,0 +1,615 @@ +{ + "metadata": { + "timestamp": "20260425_160016", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6297, + "reward_mean": 1.4818, + "success_rate": 0.8462, + "adaptation": 0.8568, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -1.1364, + "grader_score": 0.6114, + "success_score": 0.55, + "budget_score": 0.0727, + "adaptation_score": 0.8889, + "latency_score": 0.5387, + "sla_score": 1.0, + "success_rate": 0.8462, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -1.3182, + "grader_score": 0.6135, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.7946, + "latency_score": 0.5208, + "sla_score": 1.0, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": 6.3636, + "grader_score": 0.6953, + "success_score": 0.8, + "budget_score": 0.0727, + "adaptation_score": 0.8583, + "latency_score": 0.6138, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/prompt_audit/belief_v1_heldout5/eval_summary_20260425_160016.md b/eval/outputs/prompt_audit/belief_v1_heldout5/eval_summary_20260425_160016.md new file mode 100644 index 0000000000000000000000000000000000000000..2080c8fe211d15b6e427dd1cffdd226f8e752a91 --- /dev/null +++ b/eval/outputs/prompt_audit/belief_v1_heldout5/eval_summary_20260425_160016.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_160016 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6297 (n=5) | LLM +1.2 points vs heuristic | diff --git a/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_results_20260425_165910.json b/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_results_20260425_165910.json new file mode 100644 index 0000000000000000000000000000000000000000..3d58de7388777e3e30c0fe39811ef0f4bbb38bf1 --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_results_20260425_165910.json @@ -0,0 +1,1468 @@ +{ + "metadata": { + "timestamp": "20260425_165910", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "easy", + "medium", + "hard", + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2 + ] + }, + "summary": { + "easy|heuristic": { + "grader_mean": 0.7734, + "reward_mean": 9.4667, + "success_rate": 0.8833, + "adaptation": 0.8833, + "n": 3 + }, + "medium|heuristic": { + "grader_mean": 0.6187, + "reward_mean": -1.5088, + "success_rate": 0.75, + "adaptation": 0.7333, + "n": 3 + }, + "hard|heuristic": { + "grader_mean": 0.5491, + "reward_mean": -4.7909, + "success_rate": 0.7593, + "adaptation": 0.7868, + "n": 3 + }, + "hard_multi|heuristic": { + "grader_mean": 0.5937, + "reward_mean": -3.0795, + "success_rate": 0.6947, + "adaptation": 0.6407, + "n": 3 + }, + "easy|llm": { + "grader_mean": 0.7044, + "reward_mean": 7.4833, + "success_rate": 0.8952, + "adaptation": 0.8167, + "n": 3 + }, + "medium|llm": { + "grader_mean": 0.6559, + "reward_mean": 1.1492, + "success_rate": 0.8061, + "adaptation": 0.8111, + "n": 3 + }, + "hard|llm": { + "grader_mean": 0.6196, + "reward_mean": -0.7321, + "success_rate": 0.8446, + "adaptation": 0.8796, + "n": 3 + }, + "hard_multi|llm": { + "grader_mean": 0.6989, + "reward_mean": 6.2879, + "success_rate": 0.8887, + "adaptation": 0.8675, + "n": 3 + } + }, + "episodes": [ + { + "task": "easy", + "seed": 0, + "policy": "heuristic", + "total_reward": 12.2, + "grader_score": 0.7709, + "success_score": 0.95, + "budget_score": 0.04, + "adaptation_score": 0.95, + "latency_score": 0.6993, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 1, + "policy": "heuristic", + "total_reward": 10.0, + "grader_score": 0.8422, + "success_score": 0.85, + "budget_score": 0.8, + "adaptation_score": 0.85, + "latency_score": 0.7358, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95 + ] + }, + { + "task": "easy", + "seed": 2, + "policy": "heuristic", + "total_reward": 6.2, + "grader_score": 0.7071, + "success_score": 0.85, + "budget_score": 0.04, + "adaptation_score": 0.85, + "latency_score": 0.6306, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + -2.25, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "medium", + "seed": 0, + "policy": "heuristic", + "total_reward": 1.2105, + "grader_score": 0.6776, + "success_score": 0.75, + "budget_score": 0.2421, + "adaptation_score": 0.7333, + "latency_score": 0.5979, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + -2.263157894736842 + ] + }, + { + "task": "medium", + "seed": 1, + "policy": "heuristic", + "total_reward": -0.9474, + "grader_score": 0.6688, + "success_score": 0.7, + "budget_score": 0.4105, + "adaptation_score": 0.6667, + "latency_score": 0.5696, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 2, + "policy": "heuristic", + "total_reward": -4.7895, + "grader_score": 0.5097, + "success_score": 0.8, + "budget_score": 0.0, + "adaptation_score": 0.8, + "latency_score": 0.6483, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -10.0 + ] + }, + { + "task": "hard", + "seed": 0, + "policy": "heuristic", + "total_reward": -7.8824, + "grader_score": 0.4999, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.8235, + "latency_score": 0.6343, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + -10.0 + ] + }, + { + "task": "hard", + "seed": 1, + "policy": "heuristic", + "total_reward": 0.2941, + "grader_score": 0.6506, + "success_score": 0.75, + "budget_score": 0.0588, + "adaptation_score": 0.7368, + "latency_score": 0.5973, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard", + "seed": 2, + "policy": "heuristic", + "total_reward": -6.7845, + "grader_score": 0.4969, + "success_score": 0.7778, + "budget_score": 0.0, + "adaptation_score": 0.8, + "latency_score": 0.6374, + "sla_score": 0.9444, + "success_rate": 0.7778, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.3138982657484135, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4659, + "grader_score": 0.5569, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.6032, + "latency_score": 0.4686, + "sla_score": 0.9474, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.3750364951788474, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -2.7727, + "grader_score": 0.6077, + "success_score": 0.7, + "budget_score": 0.0455, + "adaptation_score": 0.6833, + "latency_score": 0.5213, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.0, + "grader_score": 0.6165, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.6357, + "latency_score": 0.4967, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "easy", + "seed": 0, + "policy": "llm", + "total_reward": 12.2, + "grader_score": 0.7709, + "success_score": 0.95, + "budget_score": 0.04, + "adaptation_score": 0.95, + "latency_score": 0.6993, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 1, + "policy": "llm", + "total_reward": 12.8, + "grader_score": 0.7902, + "success_score": 0.95, + "budget_score": 0.16, + "adaptation_score": 0.95, + "latency_score": 0.7058, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 2, + "policy": "llm", + "total_reward": -2.55, + "grader_score": 0.552, + "success_score": 0.55, + "budget_score": 0.09, + "adaptation_score": 0.55, + "latency_score": 0.5677, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "medium", + "seed": 0, + "policy": "llm", + "total_reward": 9.2632, + "grader_score": 0.7476, + "success_score": 0.9, + "budget_score": 0.0526, + "adaptation_score": 0.9333, + "latency_score": 0.6651, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157 + ] + }, + { + "task": "medium", + "seed": 1, + "policy": "llm", + "total_reward": -2.5789, + "grader_score": 0.6148, + "success_score": 0.7, + "budget_score": 0.0842, + "adaptation_score": 0.6667, + "latency_score": 0.5439, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + -2.0526315789473686, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157 + ] + }, + { + "task": "medium", + "seed": 2, + "policy": "llm", + "total_reward": -3.2368, + "grader_score": 0.6052, + "success_score": 0.45, + "budget_score": 0.2526, + "adaptation_score": 0.8333, + "latency_score": 0.5783, + "sla_score": 1.0, + "success_rate": 0.8182, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard", + "seed": 0, + "policy": "llm", + "total_reward": -2.8235, + "grader_score": 0.5954, + "success_score": 0.5, + "budget_score": 0.0353, + "adaptation_score": 0.8889, + "latency_score": 0.5615, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + 0.4117647058823529, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard", + "seed": 1, + "policy": "llm", + "total_reward": 4.6176, + "grader_score": 0.69, + "success_score": 0.75, + "budget_score": 0.0235, + "adaptation_score": 0.875, + "latency_score": 0.6823, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard", + "seed": 2, + "policy": "llm", + "total_reward": -3.9904, + "grader_score": 0.5735, + "success_score": 0.45, + "budget_score": 0.1059, + "adaptation_score": 0.875, + "latency_score": 0.556, + "sla_score": 0.9091, + "success_rate": 0.8182, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.3138982657484135, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": 4.7727, + "grader_score": 0.6818, + "success_score": 0.75, + "budget_score": 0.0545, + "adaptation_score": 0.8571, + "latency_score": 0.6358, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": 6.1364, + "grader_score": 0.6994, + "success_score": 0.8, + "budget_score": 0.0273, + "adaptation_score": 0.8786, + "latency_score": 0.648, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": 7.9545, + "grader_score": 0.7156, + "success_score": 0.85, + "budget_score": 0.0909, + "adaptation_score": 0.8667, + "latency_score": 0.6181, + "sla_score": 1.0, + "success_rate": 0.8947, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_summary_20260425_165910.md b/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_summary_20260425_165910.md new file mode 100644 index 0000000000000000000000000000000000000000..104a9e965cf4bb02d77d0fd0257be33fea10a3d6 --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_alltasks_dev3/eval_summary_20260425_165910.md @@ -0,0 +1,8 @@ +# Budget Router Evaluation — 20260425_165910 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Easy | 0.7734 (n=3) | 0.7044 (n=3) | | +| Medium | 0.6187 (n=3) | 0.6559 (n=3) | | +| Hard | 0.5491 (n=3) | 0.6196 (n=3) | | +| Hard_Multi | 0.5937 (n=3) | 0.6989 (n=3) | LLM +10.5 points vs heuristic | diff --git a/eval/outputs/prompt_audit/budget_guard_dev10/eval_results_20260425_164343.json b/eval/outputs/prompt_audit/budget_guard_dev10/eval_results_20260425_164343.json new file mode 100644 index 0000000000000000000000000000000000000000..431013ee91caf9cd11d955beb8e9fab77dba164c --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_dev10/eval_results_20260425_164343.json @@ -0,0 +1,1202 @@ +{ + "metadata": { + "timestamp": "20260425_164343", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6078, + "reward_mean": -2.9709, + "success_rate": 0.6998, + "adaptation": 0.6907, + "n": 10 + }, + "hard_multi|llm": { + "grader_mean": 0.6888, + "reward_mean": 4.7955, + "success_rate": 0.8722, + "adaptation": 0.8882, + "n": 10 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4659, + "grader_score": 0.5569, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.6032, + "latency_score": 0.4686, + "sla_score": 0.9474, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.3750364951788474, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -2.7727, + "grader_score": 0.6077, + "success_score": 0.7, + "budget_score": 0.0455, + "adaptation_score": 0.6833, + "latency_score": 0.5213, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.0, + "grader_score": 0.6165, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.6357, + "latency_score": 0.4967, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "heuristic", + "total_reward": -1.9895, + "grader_score": 0.6289, + "success_score": 0.7, + "budget_score": 0.2091, + "adaptation_score": 0.6833, + "latency_score": 0.5416, + "sla_score": 0.95, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.262190038025986, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "heuristic", + "total_reward": -4.0909, + "grader_score": 0.5933, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.6625, + "latency_score": 0.5175, + "sla_score": 1.0, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "heuristic", + "total_reward": -1.4024, + "grader_score": 0.607, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.8125, + "latency_score": 0.5142, + "sla_score": 0.9412, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.311463428136077, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "heuristic", + "total_reward": -3.7273, + "grader_score": 0.6546, + "success_score": 0.65, + "budget_score": 0.4545, + "adaptation_score": 0.6458, + "latency_score": 0.5611, + "sla_score": 1.0, + "success_rate": 0.65, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "heuristic", + "total_reward": 0.1818, + "grader_score": 0.6477, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.85, + "latency_score": 0.5613, + "sla_score": 1.0, + "success_rate": 0.7778, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "heuristic", + "total_reward": -8.3509, + "grader_score": 0.5338, + "success_score": 0.6, + "budget_score": 0.2, + "adaptation_score": 0.5682, + "latency_score": 0.4135, + "sla_score": 0.85, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.1359667972034475, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.4320645744998868, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2828540896362535, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "heuristic", + "total_reward": -1.0909, + "grader_score": 0.6315, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.7625, + "latency_score": 0.5336, + "sla_score": 1.0, + "success_rate": 0.7368, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": 4.7727, + "grader_score": 0.6818, + "success_score": 0.75, + "budget_score": 0.0545, + "adaptation_score": 0.8571, + "latency_score": 0.6358, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": 6.1364, + "grader_score": 0.6994, + "success_score": 0.8, + "budget_score": 0.0273, + "adaptation_score": 0.8786, + "latency_score": 0.648, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": 7.9545, + "grader_score": 0.7156, + "success_score": 0.85, + "budget_score": 0.0909, + "adaptation_score": 0.8667, + "latency_score": 0.6181, + "sla_score": 1.0, + "success_rate": 0.8947, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "llm", + "total_reward": 9.0455, + "grader_score": 0.7388, + "success_score": 0.9, + "budget_score": 0.0091, + "adaptation_score": 0.8944, + "latency_score": 0.6926, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "llm", + "total_reward": -1.1364, + "grader_score": 0.624, + "success_score": 0.65, + "budget_score": 0.0727, + "adaptation_score": 0.75, + "latency_score": 0.5904, + "sla_score": 1.0, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "llm", + "total_reward": 1.9091, + "grader_score": 0.6665, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.9375, + "latency_score": 0.6085, + "sla_score": 1.0, + "success_rate": 0.8667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "llm", + "total_reward": 9.3182, + "grader_score": 0.7535, + "success_score": 0.9, + "budget_score": 0.0636, + "adaptation_score": 0.9444, + "latency_score": 0.6755, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "llm", + "total_reward": 3.1818, + "grader_score": 0.673, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.9375, + "latency_score": 0.6004, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "llm", + "total_reward": 3.3636, + "grader_score": 0.6573, + "success_score": 0.7, + "budget_score": 0.0727, + "adaptation_score": 0.8661, + "latency_score": 0.5661, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "llm", + "total_reward": 3.4091, + "grader_score": 0.6783, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.95, + "latency_score": 0.5803, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/prompt_audit/budget_guard_dev10/eval_summary_20260425_164343.md b/eval/outputs/prompt_audit/budget_guard_dev10/eval_summary_20260425_164343.md new file mode 100644 index 0000000000000000000000000000000000000000..7a1007b2ef85dae2484be5cb2eb925414be80453 --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_dev10/eval_summary_20260425_164343.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_164343 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6078 (n=10) | 0.6888 (n=10) | LLM +8.1 points vs heuristic | diff --git a/eval/outputs/prompt_audit/budget_guard_heldout5/eval_results_20260425_163956.json b/eval/outputs/prompt_audit/budget_guard_heldout5/eval_results_20260425_163956.json new file mode 100644 index 0000000000000000000000000000000000000000..2310704a484246b6549f1255d3855158b9e567b8 --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_heldout5/eval_results_20260425_163956.json @@ -0,0 +1,617 @@ +{ + "metadata": { + "timestamp": "20260425_163956", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6577, + "reward_mean": 2.3818, + "success_rate": 0.8196, + "adaptation": 0.8216, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -5.6364, + "grader_score": 0.5687, + "success_score": 0.6, + "budget_score": 0.0727, + "adaptation_score": 0.6458, + "latency_score": 0.493, + "sla_score": 1.0, + "success_rate": 0.6667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": 6.4091, + "grader_score": 0.7038, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.9, + "latency_score": 0.6076, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -1.3182, + "grader_score": 0.6135, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.7946, + "latency_score": 0.5208, + "sla_score": 1.0, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": 6.3636, + "grader_score": 0.6953, + "success_score": 0.8, + "budget_score": 0.0727, + "adaptation_score": 0.8583, + "latency_score": 0.6138, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/prompt_audit/budget_guard_heldout5/eval_summary_20260425_163956.md b/eval/outputs/prompt_audit/budget_guard_heldout5/eval_summary_20260425_163956.md new file mode 100644 index 0000000000000000000000000000000000000000..219c165ec2f354083204ee4e50faa3bf0b8d6e72 --- /dev/null +++ b/eval/outputs/prompt_audit/budget_guard_heldout5/eval_summary_20260425_163956.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_163956 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6577 (n=5) | LLM +4.0 points vs heuristic | diff --git a/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192545.json b/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192545.json new file mode 100644 index 0000000000000000000000000000000000000000..8537e289d173c64b72db6b9a96ea6e8de005e57c --- /dev/null +++ b/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192545.json @@ -0,0 +1,149 @@ +{ + "metadata": { + "timestamp": "20260425_192545", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 101 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6753, + "reward_mean": 3.4091, + "success_rate": 0.8, + "adaptation": 0.7857, + "n": 1 + }, + "hard_multi|llm": { + "grader_mean": 0.7038, + "reward_mean": 6.4091, + "success_rate": 0.8889, + "adaptation": 0.9, + "n": 1 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": 6.4091, + "grader_score": 0.7038, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.9, + "latency_score": 0.6076, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192656.json b/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192656.json new file mode 100644 index 0000000000000000000000000000000000000000..b2e99c3467968a280558a7c30e7674cf725eaeef --- /dev/null +++ b/eval/outputs/trace_compare/eval_seed101/eval_results_20260425_192656.json @@ -0,0 +1,149 @@ +{ + "metadata": { + "timestamp": "20260425_192656", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 102 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6228, + "reward_mean": -2.5909, + "success_rate": 0.7, + "adaptation": 0.6932, + "n": 1 + }, + "hard_multi|llm": { + "grader_mean": 0.707, + "reward_mean": 6.0909, + "success_rate": 0.8889, + "adaptation": 0.9091, + "n": 1 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192545.md b/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192545.md new file mode 100644 index 0000000000000000000000000000000000000000..55ec0d23de68eaaa805a9bb0e2958dcbb5985482 --- /dev/null +++ b/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192545.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_192545 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6753 (n=1) | 0.7038 (n=1) | LLM +2.8 points vs heuristic | diff --git a/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192656.md b/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192656.md new file mode 100644 index 0000000000000000000000000000000000000000..530579d26231051b66d09aa3685b15092880b099 --- /dev/null +++ b/eval/outputs/trace_compare/eval_seed101/eval_summary_20260425_192656.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_192656 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6228 (n=1) | 0.7070 (n=1) | LLM +8.4 points vs heuristic | diff --git a/eval/trace_episode.py b/eval/trace_episode.py new file mode 100644 index 0000000000000000000000000000000000000000..a0dde6294635ce2312dafe0c44afa923182431d4 --- /dev/null +++ b/eval/trace_episode.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Trace one Budget Router episode for a chosen policy, task, and seed. + +This is a debugging/evidence tool: it prints per-step actions, step rewards, +costs, success/failure, latency, cumulative reward, and final grader metrics. +It does not expose hidden provider health to the policy. +""" + +from __future__ import annotations + +import json +import os +import sys +from pathlib import Path +from typing import Any, Dict, List, Optional + +import typer + +# Ensure imports work when run as `uv run python eval/trace_episode.py`. +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation, TaskConfig +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import episode_metrics, grade_episode +from budget_router.tasks import TASK_PRESETS +from inference import LLMRouter + + +app = typer.Typer(add_completion=False) + +POLICIES = {"heuristic", "llm", "ppo"} +DEFAULT_PPO_MODELS = { + "easy": Path("trained_models/ppo_easy_50k.zip"), + "hard_multi": Path("trained_models/ppo_hard_multi_100k.zip"), +} +# Matches train/gym_wrapper.BudgetRouterGymEnv action order (Discrete 0..3). +_PPO_ACTION_NAMES = ("route_to_a", "route_to_b", "route_to_c", "shed_load") + + +def _echo_step_progress( + *, + policy_label: str, + step: int, + action: str, + reward: float, + cumulative: float, + done: bool, + llm_error: Optional[str] = None, + verbose: bool, +) -> None: + if not verbose: + return + err = f" llm_error={llm_error}" if llm_error else "" + typer.echo( + f"[trace] policy={policy_label} step={step} action={action} " + f"reward={reward:+.3f} cum={cumulative:+.3f} done={done}{err}" + ) + +def _visible_observation_row(obs: Observation) -> Dict[str, float]: + """Public observation values available to the policy before it acts.""" + return { + "provider_a_status": round(float(obs.provider_a_status), 4), + "provider_b_status": round(float(obs.provider_b_status), 4), + "provider_c_status": round(float(obs.provider_c_status), 4), + "observed_budget_remaining": round(float(obs.budget_remaining), 4), + "queue_backlog": round(float(obs.queue_backlog), 4), + "system_latency": round(float(obs.system_latency), 4), + "step_count": round(float(obs.step_count), 4), + } + + +def _visible_observation_row_from_array(values: Any) -> Dict[str, float]: + """Public observation values from the Gym wrapper's 7-field observation array.""" + return { + "provider_a_status": round(float(values[0]), 4), + "provider_b_status": round(float(values[1]), 4), + "provider_c_status": round(float(values[2]), 4), + "observed_budget_remaining": round(float(values[3]), 4), + "queue_backlog": round(float(values[4]), 4), + "system_latency": round(float(values[5]), 4), + "step_count": round(float(values[6]), 4), + } + + +def _cumulative_step_rows( + history: List[Dict[str, Any]], + visible_observations: List[Dict[str, float]], +) -> List[Dict[str, Any]]: + rows: List[Dict[str, Any]] = [] + cumulative_reward = 0.0 + cumulative_cost = 0.0 + + for item in history: + reward = float(item.get("reward", 0.0) or 0.0) + cost = float(item.get("cost", 0.0) or 0.0) + initial_budget = float(item.get("initial_budget", 0.0) or 0.0) + cumulative_reward += reward + cumulative_cost += cost + budget_remaining = max(0.0, initial_budget - cumulative_cost) + + obs_row = visible_observations[len(rows)] if len(rows) < len(visible_observations) else {} + rows.append({ + "step": int(item.get("step", len(rows) + 1)), + "action": item.get("action_type"), + "provider": item.get("provider"), + "success": bool(item.get("request_succeeded", False)), + "reward": round(reward, 4), + "cumulative_reward": round(cumulative_reward, 4), + "cost": round(cost, 4), + "budget_remaining": round(budget_remaining, 4), + "latency_ms": float(item.get("latency_ms", 0.0) or 0.0), + "queue_overflow": bool(item.get("queue_overflow", False)), + "budget_exhausted": bool(item.get("budget_exhausted", False)), + **obs_row, + }) + + return rows + + +def _run_heuristic( + task_cfg: TaskConfig, seed: int, *, verbose: bool = False +) -> tuple[BudgetRouterEnv, List[Dict[str, float]]]: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=task_cfg) + visible_observations = [] + cumulative = 0.0 + while not obs.done: + visible_observations.append(_visible_observation_row(obs)) + action = heuristic_baseline_policy(obs) + action_str = action.action_type.value + obs = env.step(action) + r = float(obs.reward or 0.0) + cumulative += r + _echo_step_progress( + policy_label="heuristic", + step=int(env._internal.current_step), + action=action_str, + reward=r, + cumulative=cumulative, + done=bool(obs.done), + verbose=verbose, + ) + return env, visible_observations + + +def _run_llm( + task_name: str, task_cfg: TaskConfig, seed: int, *, verbose: bool = False +) -> tuple[BudgetRouterEnv, List[Dict[str, float]]]: + api_key = os.getenv("API_KEY") or os.getenv("HF_TOKEN") + api_base_url = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1") + model_name = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct") + if not api_key: + raise RuntimeError("LLM policy requires HF_TOKEN or API_KEY.") + + policy = LLMRouter(api_base_url=api_base_url, model_name=model_name, api_key=api_key) + policy.reset(task_name=task_name) + + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=task_cfg) + visible_observations = [] + cumulative = 0.0 + if verbose: + typer.echo( + f"[trace] begin policy=llm task={task_name} seed={seed} " + f"endpoint={api_base_url} model={model_name} " + f"(~{task_cfg.max_steps} sequential LLM calls; first call starting…)" + ) + while not obs.done: + visible_observations.append(_visible_observation_row(obs)) + action = policy.choose_action(obs) + action_str = action.action_type.value + obs = env.step(action) + r = float(obs.reward or 0.0) + cumulative += r + _echo_step_progress( + policy_label="llm", + step=int(env._internal.current_step), + action=action_str, + reward=r, + cumulative=cumulative, + done=bool(obs.done), + llm_error=policy.last_error, + verbose=verbose, + ) + return env, visible_observations + + +def _default_ppo_model_path(task_name: str) -> Path: + if task_name not in DEFAULT_PPO_MODELS: + raise ValueError( + f"No default PPO model for task '{task_name}'. " + "Pass --model-path explicitly, or use task easy/hard_multi." + ) + return DEFAULT_PPO_MODELS[task_name] + + +def _run_ppo( + task_name: str, + task_cfg: TaskConfig, + seed: int, + model_path: Optional[Path], + *, + verbose: bool = False, +) -> tuple[BudgetRouterEnv, List[Dict[str, float]]]: + # Lazy import keeps heuristic/LLM tracing available without training extras. + try: + from stable_baselines3 import PPO + from train.gym_wrapper import BudgetRouterGymEnv + except ImportError as exc: + raise RuntimeError("PPO tracing requires training dependencies: `uv sync --extra training`.") from exc + + resolved_model_path = model_path or _default_ppo_model_path(task_name) + if not resolved_model_path.exists(): + raise FileNotFoundError(f"PPO model not found: {resolved_model_path}") + + model = PPO.load(str(resolved_model_path)) + gym_env = BudgetRouterGymEnv(scenario=task_cfg, seed=seed) + obs, _ = gym_env.reset() + done = False + visible_observations = [] + cumulative = 0.0 + while not done: + visible_observations.append(_visible_observation_row_from_array(obs)) + action_idx, _ = model.predict(obs, deterministic=True) + ai = int(action_idx) + action_str = _PPO_ACTION_NAMES[ai] if 0 <= ai < len(_PPO_ACTION_NAMES) else str(ai) + obs, reward, terminated, truncated, _ = gym_env.step(ai) + r = float(reward) + cumulative += r + done = terminated or truncated + inner = gym_env._env + _echo_step_progress( + policy_label="ppo", + step=int(inner._internal.current_step), + action=action_str, + reward=r, + cumulative=cumulative, + done=done, + verbose=verbose, + ) + + return gym_env._env, visible_observations + + +def trace_episode( + task_name: str, + seed: int, + policy_name: str, + model_path: Optional[Path] = None, + *, + verbose: bool = False, +) -> Dict[str, Any]: + """Run one episode and return step rows plus final scorer outputs.""" + if task_name not in TASK_PRESETS: + raise ValueError(f"Unknown task '{task_name}'. Choose from: {sorted(TASK_PRESETS)}") + if policy_name not in POLICIES: + raise ValueError(f"Unknown policy '{policy_name}'. Choose from: {sorted(POLICIES)}") + + task_cfg = TASK_PRESETS[task_name] + if policy_name == "heuristic": + env, visible_observations = _run_heuristic(task_cfg=task_cfg, seed=seed, verbose=verbose) + elif policy_name == "llm": + env, visible_observations = _run_llm( + task_name=task_name, task_cfg=task_cfg, seed=seed, verbose=verbose + ) + else: + env, visible_observations = _run_ppo( + task_name=task_name, + task_cfg=task_cfg, + seed=seed, + model_path=model_path, + verbose=verbose, + ) + + history = env._internal.history + steps = _cumulative_step_rows(history, visible_observations) + grader = {k: round(float(v), 4) for k, v in grade_episode(history).items()} + + return { + "task": task_name, + "seed": seed, + "policy": policy_name, + "episode_length": len(steps), + "total_reward": round(sum(row["reward"] for row in steps), 4), + "grader": grader, + "metrics": episode_metrics(history), + "steps": steps, + } + + +def _print_trace(result: Dict[str, Any]) -> None: + typer.echo(f"Task={result['task']} Policy={result['policy']} Seed={result['seed']}") + typer.echo(f"Episode length={result['episode_length']} Total reward={result['total_reward']:+.4f}") + typer.echo("Grader:") + for key, value in result["grader"].items(): + typer.echo(f" {key}: {value:.4f}") + + typer.echo("") + typer.echo( + "Step | A_stat | B_stat | C_stat | Action | Provider | Success | " + "Reward | CumReward | Cost | Budget | Latency | Flags" + ) + typer.echo( + "-----|--------|--------|--------|-------------|----------|---------|" + "---------|-----------|------|--------|---------|------" + ) + for row in result["steps"]: + flags = [] + if row["queue_overflow"]: + flags.append("queue_overflow") + if row["budget_exhausted"]: + flags.append("budget_exhausted") + typer.echo( + f"{row['step']:>4} | {row.get('provider_a_status', 0.0):>6.3f} | " + f"{row.get('provider_b_status', 0.0):>6.3f} | " + f"{row.get('provider_c_status', 0.0):>6.3f} | " + f"{row['action']:<11} | {str(row['provider'] or '-'):>8} | " + f"{str(row['success']).lower():>7} | {row['reward']:>+7.2f} | " + f"{row['cumulative_reward']:>+9.2f} | {row['cost']:>4.2f} | " + f"{row['budget_remaining']:>6.2f} | {row['latency_ms']:>7.2f} | {','.join(flags) or '-'}" + ) + + +@app.command() +def main( + task: str = typer.Option("hard_multi", help=f"Task name: {' | '.join(TASK_PRESETS)}"), + seed: int = typer.Option(..., help="Exact episode seed."), + policy: str = typer.Option("heuristic", help=f"Policy: {' | '.join(sorted(POLICIES))}"), + model_path: Optional[Path] = typer.Option(None, help="PPO model path. Defaults exist for easy/hard_multi."), + output_json: Optional[Path] = typer.Option(None, help="Optional path to save the full trace JSON."), + verbose: bool = typer.Option( + False, + "--verbose", + "-v", + help="Print one line per env step during the episode (useful for slow LLM runs).", + ), +) -> None: + """Run and print a single exact-seed episode trace.""" + result = trace_episode( + task_name=task, + seed=seed, + policy_name=policy, + model_path=model_path, + verbose=verbose, + ) + _print_trace(result) + + if output_json is not None: + output_json.parent.mkdir(parents=True, exist_ok=True) + output_json.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + typer.echo(f"\nSaved trace JSON: {output_json}") + + +if __name__ == "__main__": + app() diff --git a/eval_sft.py b/eval_sft.py new file mode 100644 index 0000000000000000000000000000000000000000..448e974dd29a8fe6e4b9afbab4af2a172f7162f9 --- /dev/null +++ b/eval_sft.py @@ -0,0 +1,488 @@ +#!/usr/bin/env python3 +# /// script +# dependencies = [ +# "torch", +# "transformers>=4.45.0", +# "huggingface_hub>=0.24.0", +# "scipy", +# "budget-router @ git+https://huggingface.co/spaces/akshay4/budget-router-openenv", +# ] +# /// +"""Evaluate a Budget Router SFT model against the heuristic baseline.""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import time +from pathlib import Path +from typing import Any + +import numpy as np + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation, TaskConfig +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import episode_metrics, grade_episode +from budget_router.tasks import HARD_MULTI, TASK_PRESETS +try: + from inference import SYSTEM_PROMPT + + _SYSTEM_PROMPT_SOURCE = "inference" +except ModuleNotFoundError as exc: + if exc.name != "inference": + raise + SYSTEM_PROMPT = """ +You are a cost-aware LLM API routing agent managing a production system. +At each step, output EXACTLY ONE action string. Nothing else. + +ENVIRONMENT: + Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable). + provider_X_status = windowed success rate [0=always fails, 1=always succeeds]. + IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to + in this episode — it is unobserved, not confirmed healthy. Route to it once to get + a real reading. Do not treat 0.500 as a health signal. + budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty. + step_count [0→1], steps_remaining: episode progress (20 steps total). + +VALID ACTIONS (output ONLY one): + route_to_a | route_to_b | route_to_c | shed_load + +GOLDEN RULE — DEFAULT STRATEGY: + Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score. + +NOISE CALIBRATION (critical): +- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals. +- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps +indicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise. +- REAL degradation signal: sustained negative trend AND current status is visibly declining. +- Only when both conditions hold across consecutive observations should you consider early switching. +- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit. + + +WHEN TO SWITCH (use your conversation history): +A → B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable, + OR status_a is already below 0.52 (failure probability exceeds success probability). +B → C: Same principle — sustained decline signals, not single-step noise. +Never switch based on a single bad observation — noise causes occasional dips. + +BUDGET RUNWAY — HARD CONSTRAINT: +budget_runway_at_current_rate shows how many more steps you can afford at current spend rate. +If budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY. +If budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are + both below 0.30 status. Prefer shed_load over routing C when budget is this low. +NEVER route to any provider if doing so would leave budget_remaining below the cost of +that provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode +value accumulated so far — budget survival is non-negotiable. +TASK PROFILES (the task name appears in each observation — use it): + easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative. + medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails. + hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences. + Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10). + Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy. + CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion. + +Output only the action string.""" + _SYSTEM_PROMPT_SOURCE = "embedded_fallback" + +_AGENT_DEBUG_LOG = "/Users/akshaybabbar/Desktop/work/.cursor/debug-e4cac3.log" + + +def _agent_debug_ndjson(payload: dict[str, object]) -> None: + line = json.dumps(payload) + try: + with open(_AGENT_DEBUG_LOG, "a", encoding="utf-8") as f: + f.write(line + "\n") + except OSError: + print(f"[agent-debug] {line}", flush=True) + + +VALID_ACTIONS = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] +DEFAULT_MODEL_REPO = "akshay4/budget-router-sft-qwen1.5b" + + +def _steps_remaining(obs: Observation, max_steps: int = 20) -> int: + elapsed = int(round(float(obs.step_count) * max_steps)) + return max(0, max_steps - elapsed) + + +def _trend_text(obs: Observation, previous_obs: Observation | None, previous2_obs: Observation | None) -> str: + if previous2_obs is not None: + ta = (obs.provider_a_status - previous2_obs.provider_a_status) / 2.0 + tb = (obs.provider_b_status - previous2_obs.provider_b_status) / 2.0 + tc = (obs.provider_c_status - previous2_obs.provider_c_status) / 2.0 + return f"trend (avg/step, 2-step): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + if previous_obs is not None: + ta = obs.provider_a_status - previous_obs.provider_a_status + tb = obs.provider_b_status - previous_obs.provider_b_status + tc = obs.provider_c_status - previous_obs.provider_c_status + return f"trend (1-step only, noisy): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + return "trend: unavailable" + + +def _budget_runway_text(obs: Observation, previous_obs: Observation | None) -> str: + if previous_obs is None: + return "budget_runway_at_current_rate: >20 steps" + budget_spent = float(previous_obs.budget_remaining) - float(obs.budget_remaining) + if budget_spent <= 0.001: + return "budget_runway_at_current_rate: >20 steps" + runway = int(float(obs.budget_remaining) / budget_spent) + return f"budget_runway_at_current_rate: ~{runway} steps" + + +def _previous_step_feedback(obs: Observation) -> str: + metadata = getattr(obs, "metadata", None) or {} + if not metadata.get("action_type"): + return "" + parts = [ + "previous_step_feedback:", + f" previous_action: {metadata.get('action_type')}", + ] + if obs.reward is not None: + parts.append(f" previous_reward: {float(obs.reward):+.2f}") + if metadata.get("request_succeeded") is not None: + parts.append(f" previous_success: {str(bool(metadata.get('request_succeeded'))).lower()}") + if metadata.get("cost") is not None: + parts.append(f" previous_cost: {float(metadata.get('cost')):.2f}") + if metadata.get("latency_ms") is not None: + parts.append(f" previous_latency_ms: {float(metadata.get('latency_ms')):.2f}") + if metadata.get("budget_exhausted"): + parts.append(" previous_budget_exhausted: true") + return "\n".join(parts) + + +def format_observation_for_sft( + *, + obs: Observation, + task_name: str, + previous_obs: Observation | None, + previous2_obs: Observation | None, +) -> str: + lines = [ + f"task: {task_name}", + f"provider_a_status: {obs.provider_a_status:.3f}", + f"provider_b_status: {obs.provider_b_status:.3f}", + f"provider_c_status: {obs.provider_c_status:.3f}", + f"budget_remaining: {obs.budget_remaining:.3f}", + f"queue_backlog: {obs.queue_backlog:.3f}", + f"system_latency: {obs.system_latency:.3f}", + f"step_count: {obs.step_count:.3f}", + f"steps_remaining: {_steps_remaining(obs)}", + _trend_text(obs, previous_obs, previous2_obs), + _budget_runway_text(obs, previous_obs), + ] + feedback = _previous_step_feedback(obs) + if feedback: + lines.append(feedback) + return "\n".join(lines) + + +def parse_action(text: str) -> tuple[str, bool]: + lowered = text.strip().lower() + for action in VALID_ACTIONS: + if action in lowered: + return action, True + return "route_to_a", False + + +def apply_budget_safety_guard(action_str: str, observation: Observation, task_cfg: TaskConfig) -> str: + if action_str == "shed_load": + return action_str + costs = { + "route_to_a": task_cfg.cost_a, + "route_to_b": task_cfg.cost_b, + "route_to_c": task_cfg.cost_c, + } + selected_cost = costs.get(action_str, 0.0) + budget_dollars = float(observation.budget_remaining) * float(task_cfg.initial_budget) + if selected_cost >= budget_dollars - 1e-9: + return "shed_load" + return action_str + + +def run_heuristic_episode(task_cfg: TaskConfig, seed: int) -> dict[str, Any]: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=task_cfg) + total_reward = 0.0 + while not obs.done: + obs = env.step(heuristic_baseline_policy(obs)) + total_reward += float(obs.reward or 0.0) + grader = grade_episode(env._internal.history) + metrics = episode_metrics(env._internal.history) + return { + "grader_score": float(grader["overall_score"]), + "total_reward": total_reward, + "episode_length": env._internal.current_step, + "grader": grader, + "metrics": metrics, + } + + +class SFTPolicy: + def __init__(self, model_repo: str, *, token: str | None, use_budget_guard: bool) -> None: + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer + + self.device = "cuda" if torch.cuda.is_available() else "cpu" + dtype = torch.bfloat16 if self.device == "cuda" and torch.cuda.is_bf16_supported() else torch.float16 + self.model = AutoModelForCausalLM.from_pretrained(model_repo, torch_dtype=dtype, token=token) + self.model.to(self.device) + self.model.eval() + self.tokenizer = AutoTokenizer.from_pretrained(model_repo, token=token) + if self.tokenizer.pad_token is None: + self.tokenizer.pad_token = self.tokenizer.eos_token + self.use_budget_guard = use_budget_guard + self.messages: list[dict[str, str]] = [] + self.previous_obs: Observation | None = None + self.previous2_obs: Observation | None = None + self.parse_failures = 0 + + def reset(self) -> None: + self.messages = [{"role": "system", "content": SYSTEM_PROMPT}] + self.previous_obs = None + self.previous2_obs = None + self.parse_failures = 0 + + def choose_action(self, obs: Observation, *, task_name: str, task_cfg: TaskConfig) -> str: + import torch + + obs_text = format_observation_for_sft( + obs=obs, + task_name=task_name, + previous_obs=self.previous_obs, + previous2_obs=self.previous2_obs, + ) + self.messages.append({"role": "user", "content": obs_text}) + prompt = self.tokenizer.apply_chat_template( + self.messages, + tokenize=False, + add_generation_prompt=True, + ) + inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device) + with torch.no_grad(): + output = self.model.generate( + **inputs, + max_new_tokens=10, + do_sample=False, + pad_token_id=self.tokenizer.eos_token_id, + ) + generated = self.tokenizer.decode( + output[0][inputs["input_ids"].shape[1] :], + skip_special_tokens=True, + ) + action_str, ok = parse_action(generated) + if not ok: + self.parse_failures += 1 + if self.use_budget_guard: + action_str = apply_budget_safety_guard(action_str, obs, task_cfg) + self.messages.append({"role": "assistant", "content": action_str}) + self.previous2_obs = self.previous_obs + self.previous_obs = obs + return action_str + + +def run_sft_episode(policy: SFTPolicy, task_name: str, task_cfg: TaskConfig, seed: int) -> dict[str, Any]: + env = BudgetRouterEnv() + policy.reset() + obs = env.reset(seed=seed, scenario=task_cfg) + total_reward = 0.0 + actions: list[str] = [] + while not obs.done: + action_str = policy.choose_action(obs, task_name=task_name, task_cfg=task_cfg) + actions.append(action_str) + obs = env.step(Action(action_type=ActionType(action_str))) + total_reward += float(obs.reward or 0.0) + grader = grade_episode(env._internal.history) + metrics = episode_metrics(env._internal.history) + return { + "grader_score": float(grader["overall_score"]), + "total_reward": total_reward, + "episode_length": env._internal.current_step, + "grader": grader, + "metrics": metrics, + "actions": actions, + "parse_failures": policy.parse_failures, + } + + +def _mean(values: list[float]) -> float: + return float(sum(values) / len(values)) if values else 0.0 + + +def _sample_std(values: list[float]) -> float: + if len(values) < 2: + return 0.0 + mean = _mean(values) + return float(math.sqrt(sum((v - mean) ** 2 for v in values) / (len(values) - 1))) + + +def compute_paired_stats(heuristic_scores: list[float], sft_scores: list[float]) -> dict[str, Any]: + if len(heuristic_scores) != len(sft_scores): + raise ValueError("Paired stats require equal-length score lists.") + if not heuristic_scores: + raise ValueError("No scores provided.") + + diffs = [s - h for h, s in zip(heuristic_scores, sft_scores)] + n = len(diffs) + delta = _mean(diffs) + std_diff = _sample_std(diffs) + if std_diff == 0.0: + t_stat = math.inf if delta > 0 else (-math.inf if delta < 0 else 0.0) + p_val = 0.0 if delta > 0 else 1.0 + cohens_d = math.inf if delta > 0 else (-math.inf if delta < 0 else 0.0) + else: + try: + from scipy import stats + + t_stat, p_val = stats.ttest_rel(sft_scores, heuristic_scores, alternative="greater") + cohens_d = delta / std_diff + except Exception: + t_stat = delta / (std_diff / math.sqrt(n)) + p_val = float("nan") + cohens_d = delta / std_diff + + return { + "n_seeds": n, + "mean_heuristic": _mean(heuristic_scores), + "mean_sft": _mean(sft_scores), + "std_heuristic": _sample_std(heuristic_scores), + "std_sft": _sample_std(sft_scores), + "delta": delta, + "t_stat": float(t_stat), + "p_val": float(p_val), + "cohens_d": float(cohens_d), + "significant": bool(delta > 0 and p_val < 0.05), + "wins": sum(1 for d in diffs if d > 0), + "ties": sum(1 for d in diffs if d == 0), + "losses": sum(1 for d in diffs if d < 0), + } + + +def _ci95(values: list[float]) -> tuple[float, float]: + n = len(values) + mean = _mean(values) + if n < 2: + return mean, mean + se = _sample_std(values) / math.sqrt(n) + try: + from scipy import stats + + lo, hi = stats.t.interval(0.95, df=n - 1, loc=mean, scale=se) + return float(lo), float(hi) + except Exception: + return mean - 1.96 * se, mean + 1.96 * se + + +def _parse_seed_values(value: str | None, n_seeds: int) -> list[int]: + if value: + return [int(part) for part in value.replace(",", " ").split()] + return list(range(300, 300 + n_seeds)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Evaluate SFT Budget Router model.") + parser.add_argument("--model-repo", default=os.getenv("SFT_MODEL_REPO", DEFAULT_MODEL_REPO)) + parser.add_argument("--task", default=os.getenv("TASK_NAME", "hard_multi"), choices=sorted(TASK_PRESETS)) + parser.add_argument("--n-seeds", type=int, default=int(os.getenv("N_SEEDS", "10"))) + parser.add_argument("--seed-values", default=os.getenv("EVAL_SEED_VALUES")) + parser.add_argument("--output-json", default=os.getenv("EVAL_OUTPUT_JSON", "eval_results_sft.json")) + parser.add_argument("--no-budget-guard", action="store_true") + parser.add_argument("--no-upload", action="store_true") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + token = os.environ.get("HF_TOKEN") + task_cfg = TASK_PRESETS[args.task] + seeds = _parse_seed_values(args.seed_values, args.n_seeds) + # #region agent log + _agent_debug_ndjson( + { + "sessionId": "e4cac3", + "runId": os.environ.get("DEBUG_RUN_ID", "eval-import-fix"), + "hypothesisId": "H1", + "location": "eval_sft.py:main", + "message": "eval_startup", + "data": { + "system_prompt_source": _SYSTEM_PROMPT_SOURCE, + "model_repo": args.model_repo, + "task": args.task, + "n_seeds": len(seeds), + }, + "timestamp": int(time.time() * 1000), + } + ) + # #endregion + policy = SFTPolicy(args.model_repo, token=token, use_budget_guard=not args.no_budget_guard) + + episodes: list[dict[str, Any]] = [] + heuristic_scores: list[float] = [] + sft_scores: list[float] = [] + for seed in seeds: + heuristic_ep = run_heuristic_episode(task_cfg, seed) + sft_ep = run_sft_episode(policy, args.task, task_cfg, seed) + heuristic_scores.append(float(heuristic_ep["grader_score"])) + sft_scores.append(float(sft_ep["grader_score"])) + episodes.append({"seed": seed, "heuristic": heuristic_ep, "sft": sft_ep}) + print( + f"[eval-sft] seed={seed} heuristic={heuristic_ep['grader_score']:.4f} " + f"sft={sft_ep['grader_score']:.4f} delta={sft_ep['grader_score'] - heuristic_ep['grader_score']:+.4f} " + f"parse_failures={sft_ep['parse_failures']}", + flush=True, + ) + + stats = compute_paired_stats(heuristic_scores, sft_scores) + heu_ci = _ci95(heuristic_scores) + sft_ci = _ci95(sft_scores) + result = { + **stats, + "task": args.task, + "seeds": seeds, + "heuristic_scores": heuristic_scores, + "sft_scores": sft_scores, + "heuristic_ci95": heu_ci, + "sft_ci95": sft_ci, + "budget_guard": not args.no_budget_guard, + "episodes": episodes, + } + Path(args.output_json).write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8") + + print() + print("| Policy | Mean | Std | 95% CI | vs Heuristic |") + print("|---|---:|---:|---|---:|") + print( + f"| Heuristic | {stats['mean_heuristic']:.3f} | {stats['std_heuristic']:.3f} | " + f"[{heu_ci[0]:.3f}, {heu_ci[1]:.3f}] | baseline |" + ) + print( + f"| SFT | {stats['mean_sft']:.3f} | {stats['std_sft']:.3f} | " + f"[{sft_ci[0]:.3f}, {sft_ci[1]:.3f}] | {stats['delta']:+.3f} |" + ) + verdict = "SIGNIFICANT" if stats["significant"] else "NOT SIGNIFICANT" + print( + f"SFT: {stats['mean_sft']:.3f} vs Heuristic: {stats['mean_heuristic']:.3f} | " + f"delta={stats['delta']:+.3f} | t({stats['n_seeds'] - 1})={stats['t_stat']:.2f}, " + f"p={stats['p_val']:.4f} | {verdict} | Cohen's d={stats['cohens_d']:.2f} | " + f"wins/ties/losses={stats['wins']}/{stats['ties']}/{stats['losses']}" + ) + + if not args.no_upload: + if not token: + raise RuntimeError("HF_TOKEN must be set to upload eval JSON. Use --no-upload to skip.") + from huggingface_hub import upload_file + + upload_file( + path_or_fileobj=args.output_json, + path_in_repo=Path(args.output_json).name, + repo_id=args.model_repo, + repo_type="model", + token=token, + ) + print(f"[eval-sft] uploaded {args.output_json} to {args.model_repo}", flush=True) + + +if __name__ == "__main__": + main() diff --git a/generate_sft_data.py b/generate_sft_data.py new file mode 100644 index 0000000000000000000000000000000000000000..9a40d699ca09a59101adabafeadc73a87975b54e --- /dev/null +++ b/generate_sft_data.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 +""" +Generate SFT data for Budget Router. + +Default path is deliberately zero-API-cost: distill the existing PPO hard_multi +policy into chat transcripts, then push the dataset to the Hub for HF Jobs. + +Optional LLM labeling is available with --teacher llm, but it costs one large +model call per environment step (20 calls per episode). +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +from pathlib import Path +from typing import Any, Callable + +import numpy as np + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation, TaskConfig +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import episode_metrics, grade_episode +from budget_router.tasks import HARD_MULTI, TASK_PRESETS +from inference import LLMRouter, SYSTEM_PROMPT + + +VALID_ACTIONS = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] +PPO_ACTION_NAMES = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] +DEFAULT_DATASET_REPO = "akshay4/budget-router-sft-data" +DEFAULT_PPO_MODEL_PATH = "trained_models/ppo_hard_multi_100k.zip" +_PPO_POLICY_CACHE: dict[str, Callable[[Observation], str]] = {} + + +def _obs_to_array(obs: Observation) -> np.ndarray: + return np.array( + [ + obs.provider_a_status, + obs.provider_b_status, + obs.provider_c_status, + obs.budget_remaining, + obs.queue_backlog, + obs.system_latency, + obs.step_count, + ], + dtype=np.float32, + ) + + +def _steps_remaining(obs: Observation, max_steps: int = 20) -> int: + elapsed = int(round(float(obs.step_count) * max_steps)) + return max(0, max_steps - elapsed) + + +def _trend_text(obs: Observation, previous_obs: Observation | None, previous2_obs: Observation | None) -> str: + if previous2_obs is not None: + ta = (obs.provider_a_status - previous2_obs.provider_a_status) / 2.0 + tb = (obs.provider_b_status - previous2_obs.provider_b_status) / 2.0 + tc = (obs.provider_c_status - previous2_obs.provider_c_status) / 2.0 + return f"trend (avg/step, 2-step): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + if previous_obs is not None: + ta = obs.provider_a_status - previous_obs.provider_a_status + tb = obs.provider_b_status - previous_obs.provider_b_status + tc = obs.provider_c_status - previous_obs.provider_c_status + return f"trend (1-step only, noisy): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + return "trend: unavailable" + + +def _budget_runway_text(obs: Observation, previous_obs: Observation | None) -> str: + if previous_obs is None: + return "budget_runway_at_current_rate: >20 steps" + budget_spent = float(previous_obs.budget_remaining) - float(obs.budget_remaining) + if budget_spent <= 0.001: + return "budget_runway_at_current_rate: >20 steps" + runway = int(float(obs.budget_remaining) / budget_spent) + return f"budget_runway_at_current_rate: ~{runway} steps" + + +def _previous_step_feedback(obs: Observation) -> str: + metadata = getattr(obs, "metadata", None) or {} + if not metadata.get("action_type"): + return "" + + parts = [ + "previous_step_feedback:", + f" previous_action: {metadata.get('action_type')}", + ] + if obs.reward is not None: + parts.append(f" previous_reward: {float(obs.reward):+.2f}") + if metadata.get("request_succeeded") is not None: + parts.append(f" previous_success: {str(bool(metadata.get('request_succeeded'))).lower()}") + if metadata.get("cost") is not None: + parts.append(f" previous_cost: {float(metadata.get('cost')):.2f}") + if metadata.get("latency_ms") is not None: + parts.append(f" previous_latency_ms: {float(metadata.get('latency_ms')):.2f}") + if metadata.get("budget_exhausted"): + parts.append(" previous_budget_exhausted: true") + return "\n".join(parts) + + +def format_observation_for_sft( + *, + obs: Observation, + task_name: str, + previous_obs: Observation | None, + previous2_obs: Observation | None, +) -> str: + """Public observation text used consistently for SFT train/eval.""" + lines = [ + f"task: {task_name}", + f"provider_a_status: {obs.provider_a_status:.3f}", + f"provider_b_status: {obs.provider_b_status:.3f}", + f"provider_c_status: {obs.provider_c_status:.3f}", + f"budget_remaining: {obs.budget_remaining:.3f}", + f"queue_backlog: {obs.queue_backlog:.3f}", + f"system_latency: {obs.system_latency:.3f}", + f"step_count: {obs.step_count:.3f}", + f"steps_remaining: {_steps_remaining(obs)}", + _trend_text(obs, previous_obs, previous2_obs), + _budget_runway_text(obs, previous_obs), + ] + feedback = _previous_step_feedback(obs) + if feedback: + lines.append(feedback) + return "\n".join(lines) + + +def run_heuristic_episode(task_cfg: TaskConfig, seed: int) -> dict[str, Any]: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=task_cfg) + total_reward = 0.0 + while not obs.done: + obs = env.step(heuristic_baseline_policy(obs)) + total_reward += float(obs.reward or 0.0) + grader = grade_episode(env._internal.history) + return { + "grader_score": float(grader["overall_score"]), + "total_reward": total_reward, + "grader": grader, + } + + +def _load_ppo_policy(model_path: str) -> Callable[[Observation], str]: + if model_path in _PPO_POLICY_CACHE: + return _PPO_POLICY_CACHE[model_path] + + try: + from stable_baselines3 import PPO + except ImportError as exc: + raise RuntimeError( + "PPO teacher requires training dependencies. Run `uv sync --extra training` " + "or use --teacher heuristic/llm." + ) from exc + + path = Path(model_path) + if not path.exists(): + raise FileNotFoundError(f"PPO model not found: {path}") + model = PPO.load(str(path)) + + def choose(obs: Observation) -> str: + action_idx, _ = model.predict(_obs_to_array(obs), deterministic=True) + idx = int(action_idx) + return PPO_ACTION_NAMES[idx] if 0 <= idx < len(PPO_ACTION_NAMES) else "shed_load" + + _PPO_POLICY_CACHE[model_path] = choose + return choose + + +def _load_llm_policy(task_name: str) -> Callable[[Observation], str]: + api_key = os.environ.get("HF_TOKEN") or os.environ.get("API_KEY") + if not api_key: + raise RuntimeError("LLM teacher requires HF_TOKEN or API_KEY in the environment.") + router = LLMRouter( + api_base_url=os.environ.get("API_BASE_URL", "https://router.huggingface.co/v1"), + model_name=os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct"), + api_key=api_key, + ) + router.reset(task_name=task_name) + + def choose(obs: Observation) -> str: + return router.choose_action(obs).action_type.value + + return choose + + +def collect_teacher_episode( + *, + task_name: str, + task_cfg: TaskConfig, + seed: int, + teacher: str, + ppo_model_path: str, +) -> dict[str, Any]: + if teacher == "ppo": + choose_action = _load_ppo_policy(ppo_model_path) + elif teacher == "heuristic": + choose_action = lambda obs: heuristic_baseline_policy(obs).action_type.value + elif teacher == "llm": + choose_action = _load_llm_policy(task_name) + else: + raise ValueError(f"Unknown teacher {teacher!r}") + + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=task_cfg) + messages = [{"role": "system", "content": SYSTEM_PROMPT}] + previous2_obs: Observation | None = None + previous_obs: Observation | None = None + actions: list[str] = [] + total_reward = 0.0 + + while not obs.done: + obs_text = format_observation_for_sft( + obs=obs, + task_name=task_name, + previous_obs=previous_obs, + previous2_obs=previous2_obs, + ) + action_str = choose_action(obs) + if action_str not in VALID_ACTIONS: + action_str = "shed_load" + + messages.append({"role": "user", "content": obs_text}) + messages.append({"role": "assistant", "content": action_str}) + actions.append(action_str) + + previous2_obs = previous_obs + previous_obs = obs + obs = env.step(Action(action_type=ActionType(action_str))) + total_reward += float(obs.reward or 0.0) + + grader = grade_episode(env._internal.history) + return { + "seed": seed, + "teacher": teacher, + "messages": messages, + "actions": actions, + "grader_score": float(grader["overall_score"]), + "total_reward": total_reward, + "grader": grader, + "metrics": episode_metrics(env._internal.history), + } + + +def select_training_rows( + episodes: list[dict[str, Any]], + *, + top_fraction: float, + min_keep: int, + min_delta: float, +) -> list[dict[str, Any]]: + ranked = sorted(episodes, key=lambda item: float(item["delta_vs_heuristic"]), reverse=True) + target = max(min_keep, int(math.ceil(len(ranked) * top_fraction))) + positive = [ep for ep in ranked if float(ep["delta_vs_heuristic"]) >= min_delta] + source = positive if len(positive) >= min_keep else ranked + return source[: min(target, len(source))] + + +def write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8") as f: + for row in rows: + f.write(json.dumps(row, sort_keys=True) + "\n") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Generate Budget Router SFT dataset.") + parser.add_argument("--teacher", choices=["ppo", "heuristic", "llm"], default=os.getenv("TEACHER_POLICY", "ppo")) + parser.add_argument("--task", default=os.getenv("TASK_NAME", "hard_multi"), choices=sorted(TASK_PRESETS)) + parser.add_argument("--start-seed", type=int, default=int(os.getenv("SFT_START_SEED", "1000"))) + parser.add_argument("--n-episodes", type=int, default=int(os.getenv("SFT_N_EPISODES", "100"))) + parser.add_argument("--top-fraction", type=float, default=float(os.getenv("SFT_TOP_FRACTION", "0.30"))) + parser.add_argument("--min-keep", type=int, default=int(os.getenv("SFT_MIN_KEEP", "20"))) + parser.add_argument("--min-delta", type=float, default=float(os.getenv("SFT_MIN_DELTA", "0.0"))) + parser.add_argument("--ppo-model-path", default=os.getenv("PPO_MODEL_PATH", DEFAULT_PPO_MODEL_PATH)) + parser.add_argument("--dataset-repo", default=os.getenv("DATASET_REPO", DEFAULT_DATASET_REPO)) + parser.add_argument("--local-jsonl", default=os.getenv("SFT_LOCAL_JSONL", "outputs/sft_dataset.jsonl")) + parser.add_argument("--no-push", action="store_true", help="Write local JSONL only; do not push to Hub.") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + task_cfg = TASK_PRESETS[args.task] + seeds = list(range(args.start_seed, args.start_seed + args.n_episodes)) + + if args.teacher == "llm": + print( + f"[sft-data] teacher=llm n_episodes={args.n_episodes}; " + f"expected large-model calls <= {args.n_episodes * task_cfg.max_steps}", + flush=True, + ) + else: + print(f"[sft-data] teacher={args.teacher} uses 0 large-LLM calls", flush=True) + + episodes: list[dict[str, Any]] = [] + for i, seed in enumerate(seeds, start=1): + teacher_ep = collect_teacher_episode( + task_name=args.task, + task_cfg=task_cfg, + seed=seed, + teacher=args.teacher, + ppo_model_path=args.ppo_model_path, + ) + heuristic_ep = run_heuristic_episode(task_cfg, seed) + delta = teacher_ep["grader_score"] - heuristic_ep["grader_score"] + teacher_ep["heuristic_score"] = heuristic_ep["grader_score"] + teacher_ep["delta_vs_heuristic"] = delta + episodes.append(teacher_ep) + print( + f"[sft-data] {i:03d}/{len(seeds)} seed={seed} " + f"teacher={teacher_ep['grader_score']:.4f} heuristic={heuristic_ep['grader_score']:.4f} " + f"delta={delta:+.4f}", + flush=True, + ) + + kept = select_training_rows( + episodes, + top_fraction=args.top_fraction, + min_keep=args.min_keep, + min_delta=args.min_delta, + ) + dataset_rows = [ + { + "messages": ep["messages"], + "seed": ep["seed"], + "teacher": ep["teacher"], + "teacher_score": ep["grader_score"], + "heuristic_score": ep["heuristic_score"], + "delta_vs_heuristic": ep["delta_vs_heuristic"], + "actions": ep["actions"], + } + for ep in kept + ] + write_jsonl(Path(args.local_jsonl), dataset_rows) + + mean_all = sum(float(ep["grader_score"]) for ep in episodes) / len(episodes) + mean_kept = sum(float(ep["grader_score"]) for ep in kept) / len(kept) + mean_delta = sum(float(ep["delta_vs_heuristic"]) for ep in kept) / len(kept) + print( + "[sft-data] summary " + f"generated={len(episodes)} kept={len(kept)} mean_all={mean_all:.4f} " + f"mean_kept={mean_kept:.4f} mean_delta_kept={mean_delta:+.4f} " + f"local_jsonl={args.local_jsonl}", + flush=True, + ) + + if not args.no_push: + token = os.environ.get("HF_TOKEN") + if not token: + raise RuntimeError("HF_TOKEN must be set to push the dataset. Use --no-push for local only.") + from datasets import Dataset + + Dataset.from_list(dataset_rows).push_to_hub(args.dataset_repo, token=token) + print(f"[sft-data] pushed dataset to https://huggingface.co/datasets/{args.dataset_repo}", flush=True) + + +if __name__ == "__main__": + main() diff --git a/gradio_ui/__init__.py b/gradio_ui/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/gradio_ui/config.py b/gradio_ui/config.py new file mode 100644 index 0000000000000000000000000000000000000000..9318af138836a5bb60082e56d69c61b71888374f --- /dev/null +++ b/gradio_ui/config.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +MAX_STEPS = 20 + +SCENARIOS = ["easy", "medium", "hard", "hard_multi"] + +POLICY_CHOICES = [ + ("Heuristic", "heuristic"), + ("LLM", "llm"), +] + +try: + import stable_baselines3 # type: ignore # noqa: F401 + + POLICY_CHOICES.append(("PPO (hard_multi)", "ppo")) +except Exception: + pass + +PPO_MODEL_PATH = "trained_models/ppo_hard_multi_100k.zip" diff --git a/gradio_ui/legacy_api.py b/gradio_ui/legacy_api.py new file mode 100644 index 0000000000000000000000000000000000000000..e001f0cb39146f52984d82aba0a28f02dd2a4249 --- /dev/null +++ b/gradio_ui/legacy_api.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import Dict, Optional, Tuple + +import requests + +BASE_URL = "http://localhost:8000" +AUTO_PLAY_DELAY = 0.5 + + +class APIClient: + """Single-responsibility HTTP client for the OpenEnv Budget Router API.""" + + def __init__(self, base_url: str = BASE_URL) -> None: + self.base_url = base_url.rstrip("/") + + def _post(self, path: str, body: Dict) -> Tuple[Optional[Dict], Optional[str]]: + try: + r = requests.post(f"{self.base_url}{path}", json=body, timeout=15) + r.raise_for_status() + return r.json(), None + except Exception as exc: + return None, str(exc) + + def _get(self, path: str) -> Tuple[Optional[Dict], Optional[str]]: + try: + r = requests.get(f"{self.base_url}{path}", timeout=10) + r.raise_for_status() + return r.json(), None + except Exception as exc: + return None, str(exc) + + @staticmethod + def _normalize(payload: Dict): + """Handle both flat and observation-wrapped response shapes.""" + obs = payload.get("observation", payload) + reward = float(payload.get("reward", obs.get("reward", 0.0)) or 0.0) + meta = payload.get("metadata", obs.get("metadata", {})) or {} + done = bool(payload.get("done", obs.get("done", False))) + return obs, reward, meta, done + + def reset(self, seed: int, scenario: str): + data, err = self._post("/reset", {"seed": seed, "scenario": scenario}) + if err: + return None, err + obs, _, _, _ = self._normalize(data) + return obs, None + + def step(self, action_type: str): + data, err = self._post("/step", {"action_type": action_type}) + if err: + return None, err + return self._normalize(data), None + + def state(self): + return self._get("/state") diff --git a/gradio_ui/policies.py b/gradio_ui/policies.py new file mode 100644 index 0000000000000000000000000000000000000000..cf6fed9bb3f9917960ad8abf24f2580260412485 --- /dev/null +++ b/gradio_ui/policies.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional, Protocol, Tuple + +from budget_router.models import Action, ActionType, Observation +from inference import select_policy + +from .config import PPO_MODEL_PATH + +_PPO_MODEL: Any = None + + +class Policy(Protocol): + def choose_action(self, obs: Dict) -> str: + ... + + +class PolicyRunnerAdapter: + def __init__(self, policy_name: str, policy_impl: object) -> None: + self._policy_name = policy_name + self._policy_impl = policy_impl + + def reset(self, scenario_name: str = "") -> None: + reset_fn = getattr(self._policy_impl, "reset", None) + if not callable(reset_fn): + return + try: + reset_fn(task_name=scenario_name) + except TypeError: + reset_fn() + + def choose_action(self, obs: Dict) -> str: + observation = _obs_to_observation(obs) + if self._policy_name == "heuristic": + action = self._policy_impl(observation) + else: + action = self._policy_impl.choose_action(observation) + return action.action_type.value + + +def _obs_to_observation(obs: Dict) -> Observation: + return Observation( + provider_a_status=float(obs.get("provider_a_status", 0.0) or 0.0), + provider_b_status=float(obs.get("provider_b_status", 0.0) or 0.0), + provider_c_status=float(obs.get("provider_c_status", 0.0) or 0.0), + budget_remaining=float(obs.get("budget_remaining", 0.0) or 0.0), + queue_backlog=float(obs.get("queue_backlog", 0.0) or 0.0), + system_latency=float(obs.get("system_latency", 0.0) or 0.0), + step_count=float(obs.get("step_count", 0.0) or 0.0), + done=bool(obs.get("done", False)), + reward=float(obs.get("reward", 0.0) or 0.0), + metadata=obs.get("metadata", {}) or {}, + ) + + +def _format_policy_error(policy_name: str, error: str) -> str: + if policy_name == "llm" and "API_BASE_URL" in error and "API_KEY" in error: + return ( + "LLM auto-play is not enabled in this hosted Space. " + "To try it, duplicate this Space to your own account and set " + "API_BASE_URL, API_KEY, and optionally MODEL_NAME in that copy's Space settings." + ) + return error + + +def get_policy_runner(policy_name: str) -> Tuple[Optional[Policy], Optional[str]]: + if policy_name == "ppo": + model, err = _load_ppo_model() + if err: + return None, err + return PolicyRunnerAdapter(policy_name, PPOPolicy(model)), None + try: + return PolicyRunnerAdapter(policy_name, select_policy(policy_name)), None + except Exception as exc: + return None, _format_policy_error(policy_name, str(exc)) + + +def _load_ppo_model() -> Tuple[Optional[Any], Optional[str]]: + global _PPO_MODEL + if _PPO_MODEL is not None: + return _PPO_MODEL, None + try: + from pathlib import Path + + if not Path(PPO_MODEL_PATH).exists(): + return None, f"PPO model not found at {PPO_MODEL_PATH}." + from stable_baselines3 import PPO # type: ignore + + _PPO_MODEL = PPO.load(PPO_MODEL_PATH) + return _PPO_MODEL, None + except ModuleNotFoundError as exc: + if getattr(exc, "name", None) == "stable_baselines3": + return None, "PPO policy requires stable_baselines3. Install the 'training' extra to enable it." + return None, str(exc) + except Exception as exc: + return None, str(exc) + + +class PPOPolicy: + def __init__(self, model: Any): + self._model = model + + def choose_action(self, observation: Observation) -> Action: + try: + import numpy as np # type: ignore + + obs_arr = np.array( + [ + observation.provider_a_status, + observation.provider_b_status, + observation.provider_c_status, + observation.budget_remaining, + observation.queue_backlog, + observation.system_latency, + observation.step_count, + ], + dtype=np.float32, + ) + except Exception: + obs_arr = [ + observation.provider_a_status, + observation.provider_b_status, + observation.provider_c_status, + observation.budget_remaining, + observation.queue_backlog, + observation.system_latency, + observation.step_count, + ] + action_idx, _ = self._model.predict(obs_arr, deterministic=True) + idx = int(action_idx) + if idx == 0: + at = ActionType.ROUTE_TO_A + elif idx == 1: + at = ActionType.ROUTE_TO_B + elif idx == 2: + at = ActionType.ROUTE_TO_C + else: + at = ActionType.SHED_LOAD + return Action(action_type=at) diff --git a/gradio_ui/renderers.py b/gradio_ui/renderers.py new file mode 100644 index 0000000000000000000000000000000000000000..9ac901c6ae232260eefb8b9ec0f086f18b5ceb43 --- /dev/null +++ b/gradio_ui/renderers.py @@ -0,0 +1,1167 @@ +from __future__ import annotations + +import html +import math +import re +from typing import Any, Dict, List, Optional, Tuple + +from budget_router.reward import grade_episode +from budget_router.tasks import TASK_PRESETS + +from .config import MAX_STEPS + +# ─── Grade Computation ──────────────────────────────────────────────────────── + +MISSION_SCORE_LABEL = "Benchmark Score" +MISSION_SCORE_HELP = "Headline evaluation metric: weighted success, latency, budget, SLA, and adaptation." + +def compute_grade(history: List[Dict]) -> Dict[str, float]: + canonical_history = [ + { + "step": h.get("step", 0), + "action_type": h.get("action", "shed_load"), + "request_succeeded": h.get("succeeded", False), + "cost": h.get("cost", 0.0), + "latency_ms": h.get("latency_ms", 0.0), + "reward": h.get("reward", 0.0), + "sla_ceiling_ms": h.get("sla_ceiling_ms", 500.0), + "initial_budget": h.get("initial_budget", 1.0), + "degradation_start_step": h.get("degradation_start_step", 999), + "secondary_degradation_start_step": h.get("secondary_degradation_start_step"), + } + for h in history + ] + return grade_episode(canonical_history) + +# ─── HTML Renderers ─────────────────────────────────────────────────────────── + +_TABLE_STYLE = "width:100%;border-collapse:collapse;font-size:13px;background:#ffffff;color:#111827" +_HEADER_ROW_STYLE = "background:#fafafa" +_HEADER_CELL_STYLE = "border:1px solid #eee;padding:6px;color:#111827" +_CELL_STYLE = "border:1px solid #eee;padding:6px;text-align:center;color:#111827;background:#ffffff" +_ACTION_CELL_STYLE = "border:1px solid #eee;padding:6px;font-weight:600;color:#111827" + + +def _join(parts: List[str]) -> str: + return "".join(parts) + + +def _th(value: str) -> str: + return f"{value}" + + +def _td(value: str, style: str, colspan: Optional[int] = None) -> str: + span = f" colspan='{colspan}'" if colspan else "" + return f"{value}" + + +def _tr(cells: List[str], style: Optional[str] = None) -> str: + style_attr = f" style='{style}'" if style else "" + return f"" + "".join(cells) + "" + + +def _table(head: str, body: str) -> str: + return f"{head}{body}
" + + +def _bar(value: float, label: str, color: str, show_dollar: bool = False) -> str: + pct = max(0, min(100, int(value * 100))) + display = f"${value:.2f}" if show_dollar else f"{pct}%" + return ( + f'
' + f'
' + f'{label}{display}
' + f'
' + f'
' + ) + + +def _budget_color(remaining: float) -> str: + if remaining > 0.5: + return "#27ae60" + if remaining > 0.2: + return "#f39c12" + return "#e74c3c" + + +def _format_percent_precise(value: float) -> str: + # Show up to 4 decimal places without forcing integer rounding. + return f"{value * 100:.4f}".rstrip("0").rstrip(".") + "%" + + +def render_providers(obs: Dict) -> str: + return _join( + [ + _bar(obs.get("provider_a_status", 0), "Provider A", "#27ae60"), + _bar(obs.get("provider_b_status", 0), "Provider B", "#e67e22"), + _bar(obs.get("provider_c_status", 0), "Provider C", "#e74c3c"), + ] + ) + + +def render_budget(obs: Dict) -> str: + b = obs.get("budget_remaining", 1.0) + return _bar(b, "Budget Remaining", _budget_color(b)) + + +def render_grader(grade: Dict) -> str: + o = grade["overall_score"] + color = "#27ae60" if o > 0.7 else "#f39c12" if o > 0.4 else "#e74c3c" + return ( + f'
' + f'
' + f'{MISSION_SCORE_LABEL}: {_format_percent_precise(float(o))}
' + f'
{MISSION_SCORE_HELP}
' + f'
' + ) + + +def _GRADER_PENDING() -> str: + return "
Shown when episode completes.
" + + +def _REWARD_PENDING() -> str: + return "
Shown when episode completes.
" + + +def _PROVIDER_EMPTY() -> str: + return "
Start an episode to see provider health.
" + + +def render_episode_total_reward(history: List[Dict]) -> str: + total = 0.0 + for h in history or []: + total += float(h.get("reward", 0.0) or 0.0) + color = "#27ae60" if total >= 0 else "#e74c3c" + return ( + f'
Total Reward: {total:+.2f}
' + ) + + +def _task_config(scenario_name: str): + return TASK_PRESETS.get(scenario_name, TASK_PRESETS["easy"]) + + +def _common_provider_health(config: Any, step: int) -> Dict[str, float]: + health = { + "A": float(getattr(config, "reliability_a", 0.0) or 0.0), + "B": float(getattr(config, "reliability_b", 0.0) or 0.0), + "C": float(getattr(config, "reliability_c", 0.0) or 0.0), + } + + start_raw = getattr(config, "degradation_start_step", 999) + start = 999 if start_raw is None else int(start_raw) + target = str(getattr(config, "degradation_target", "A") or "A") + rate = float(getattr(config, "degradation_rate", 0.0) or 0.0) + if start < 999 and step >= start: + count = step if start == 0 else max(0, step - start + 1) + health[target] = max(0.05, float(health.get(target, 1.0)) - rate * count) + + secondary_start = getattr(config, "secondary_degradation_start_step", 999) + secondary_target = str(getattr(config, "secondary_degradation_target", "") or "") + secondary_rate = float(getattr(config, "secondary_degradation_rate", 0.0) or 0.0) + if ( + secondary_target + and secondary_start is not None + and int(secondary_start) < 999 + and step >= int(secondary_start) + ): + count2 = max(0, step - int(secondary_start) + 1) + health[secondary_target] = max( + 0.05, float(health.get(secondary_target, 1.0)) - secondary_rate * count2 + ) + + return health + + +def render_common_providers(health: Dict[str, float]) -> str: + return _join( + [ + _bar(float(health.get("A", 0.0)), "Provider A", "#27ae60"), + _bar(float(health.get("B", 0.0)), "Provider B", "#e67e22"), + _bar(float(health.get("C", 0.0)), "Provider C", "#e74c3c"), + ] + ) + + +def _pct(n: int, d: int) -> float: + if d <= 0: + return 0.0 + return 100.0 * (n / d) + + +def _summary_metrics(history: List[Dict]) -> Dict[str, float]: + routing = [h for h in history if h.get("action") != "shed_load"] + failures = sum(1 for h in routing if not bool(h.get("succeeded", False))) + breaches = sum( + 1 + for h in routing + if float(h.get("latency_ms", 0.0) or 0.0) + > float(h.get("sla_ceiling_ms", 500.0) or 500.0) + ) + latencies = [float(h.get("latency_ms", 0.0) or 0.0) for h in routing] + avg_latency = (sum(latencies) / len(latencies)) if latencies else 0.0 + return { + "failed_pct": _pct(failures, len(routing)), + "sla_breach_pct": _pct(breaches, len(routing)), + "avg_latency_ms": avg_latency, + } + + +def _is_finite_number(value: Any) -> bool: + if isinstance(value, bool): + return False + if not isinstance(value, (int, float)): + return False + return math.isfinite(float(value)) + + +def _fmt_pct(ok: int, total: int) -> str: + if total <= 0: + return "—" + return f"{(100.0 * ok / total):.1f}% ({ok}/{total})" + + +def render_data_quality_panel(history: List[Dict]) -> str: + try: + if not history: + return ( + "
" + "Data quality (optional)" + "
No steps yet.
" + "
" + ) + + schema_failed: List[Tuple[int, str]] = [] + consistency_failed: List[Tuple[int, str]] = [] + + prev_budget: Optional[float] = None + + for idx, h in enumerate(history): + step_raw = h.get("step", idx + 1) + try: + step = int(step_raw) + except Exception: + step = idx + 1 + + action = h.get("action") + budget = h.get("budget") + meta_raw = h.get("meta_raw") + + schema_errs: List[str] = [] + if action not in {"route_to_a", "route_to_b", "route_to_c", "shed_load"}: + schema_errs.append(f"invalid action={action!r}") + + if not isinstance(meta_raw, dict): + schema_errs.append("meta_raw missing/invalid") + meta_raw = {} + + required_meta_keys = { + "step", + "action_type", + "request_succeeded", + "cost", + "latency_ms", + "sla_ceiling_ms", + "initial_budget", + "degradation_start_step", + } + missing_meta = [k for k in sorted(required_meta_keys) if k not in meta_raw] + if missing_meta: + schema_errs.append("missing meta: " + ", ".join(missing_meta)) + + meta_action = meta_raw.get("action_type") + if meta_action is not None and meta_action != action: + schema_errs.append(f"meta action_type={meta_action!r} != action={action!r}") + + meta_step = meta_raw.get("step") + try: + meta_step_i = int(meta_step) if meta_step is not None else None + except Exception: + meta_step_i = None + schema_errs.append("meta step not int") + + if meta_step_i is not None and meta_step_i != step: + schema_errs.append(f"meta step={meta_step_i} != history step={step}") + + succeeded_raw = meta_raw.get("request_succeeded") + cost_raw = meta_raw.get("cost") + latency_raw = meta_raw.get("latency_ms") + sla_raw = meta_raw.get("sla_ceiling_ms") + init_b_raw = meta_raw.get("initial_budget") + + if not isinstance(succeeded_raw, bool): + schema_errs.append("meta request_succeeded not bool") + + if not _is_finite_number(cost_raw) or float(cost_raw) < 0: + schema_errs.append("meta cost not finite >= 0") + + if not _is_finite_number(latency_raw) or float(latency_raw) < 0: + schema_errs.append("meta latency_ms not finite >= 0") + + if not _is_finite_number(sla_raw) or float(sla_raw) <= 0: + schema_errs.append("meta sla_ceiling_ms not finite > 0") + + if not _is_finite_number(init_b_raw) or float(init_b_raw) <= 0: + schema_errs.append("meta initial_budget not finite > 0") + + if not _is_finite_number(budget) or float(budget) < 0: + schema_errs.append("budget not finite >= 0") + + if schema_errs: + schema_failed.append((step, "; ".join(schema_errs))) + continue + + b = float(budget) + c = float(cost_raw) + ib = float(init_b_raw) + + consistency_errs: List[str] = [] + if step != (idx + 1): + consistency_errs.append(f"step mismatch: got {step}, expected {idx + 1}") + + if b > 1.0 + 1e-6: + consistency_errs.append("budget > 1.0") + + if action == "shed_load": + if succeeded_raw is not False: + consistency_errs.append("shed_load succeeded must be False") + if abs(c - 0.0) > 1e-6: + consistency_errs.append("shed_load cost must be 0") + if abs(float(latency_raw) - 0.0) > 1e-6: + consistency_errs.append("shed_load latency_ms must be 0") + + if prev_budget is not None: + if action == "shed_load": + expected = prev_budget + else: + burn = (c / ib) if ib > 0 else 0.0 + expected = max(0.0, prev_budget - burn) + if abs(b - expected) > 1e-4: + consistency_errs.append( + f"budget mismatch: got {b:.4f}, expected {expected:.4f}" + ) + prev_budget = b + + if consistency_errs: + consistency_failed.append((step, "; ".join(consistency_errs))) + + total_steps = len(history) + schema_ok = total_steps - len(schema_failed) + consistency_ok = total_steps - len(consistency_failed) + + summary = _kpi_grid( + [ + ("Schema valid", _fmt_pct(schema_ok, total_steps)), + ("Consistency OK", _fmt_pct(consistency_ok, total_steps)), + ("Violations", str(len(schema_failed) + len(consistency_failed))), + ] + ) + + failures: List[Tuple[int, str, str]] = [ + (s, "schema", msg) for s, msg in schema_failed + ] + [(s, "consistency", msg) for s, msg in consistency_failed] + failures.sort(key=lambda x: (x[0], x[1])) + + if not failures: + fail_html = "
No violations detected.
" + else: + max_rows = 12 + rows = [] + head = _tr([ + _th("Step"), + _th("Type"), + _th("Reason"), + ], style=_HEADER_ROW_STYLE) + for s, kind, msg in failures[:max_rows]: + rows.append( + _tr( + [ + _td(str(s), _CELL_STYLE), + _td(kind, _CELL_STYLE), + _td(html.escape(msg), f"{_CELL_STYLE};text-align:left"), + ] + ) + ) + body = "".join(rows) + extra = "" + if len(failures) > max_rows: + extra = ( + f"
" + f"Showing {max_rows} of {len(failures)} violations." + f"
" + ) + fail_html = _table(head, body) + extra + + return ( + "
" + "Data quality (optional)" + f"
{summary}
" + f"
{fail_html}
" + "
" + ) + except Exception as exc: + return ( + "
" + "Data quality (optional)" + f"
Error computing data quality: {html.escape(str(exc))}
" + "
" + ) + + +def _step_badges(last: Optional[Dict]) -> str: + if not last: + return "
No steps yet.
" + action = str(last.get("action") or "") + if action == "shed_load": + return ( + "
" + "SHED LOAD" + "SLA N/A" + "
" + ) + succeeded = bool(last.get("succeeded", False)) + latency_ms = float(last.get("latency_ms", 0.0) or 0.0) + sla_ms = float(last.get("sla_ceiling_ms", 500.0) or 500.0) + breach = latency_ms > sla_ms + s_color = "#16a34a" if succeeded else "#dc2626" + s_text = "SUCCESS" if succeeded else "FAILED" + b_color = "#dc2626" if breach else "#16a34a" + b_text = "SLA BREACH" if breach else "SLA OK" + return ( + "
" + f"{s_text}" + f"{b_text}" + "
" + ) + + +_ACTION_COLORS = { + "route_to_a": "#d5f5e3", + "route_to_b": "#fef9e7", + "route_to_c": "#fadbd8", + "shed_load": "#f2f3f4", +} + + +def _normalize_action_label(action_value: Any) -> str: + raw = str(action_value or "shed_load").strip().lower() + if not raw: + return "shed_load" + matches = re.findall(r"route_to_[abc]|shed_load", raw) + if matches: + return matches[0] + return raw.split()[0] + + +def render_history_table_compare(history: List[Dict]) -> str: + headers = [ + "Step", + "Action", + "Provider A
Health", + "Provider B
Health", + "Provider C
Health", + "OK", + "SLA", + "Latency
(ms)", + "Budget", + "Reward", + ] + head = _tr([_th(h) for h in headers], style=_HEADER_ROW_STYLE) + table_style = ( + "width:100%;border-collapse:collapse;table-layout:fixed;" + "font-size:12px;background:#ffffff;color:#111827" + ) + colgroup = ( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ) + cell_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px" + step_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px" + action_style = _ACTION_CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px" + health_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px" + if not history: + body = _tr( + [_td("No steps yet.", "padding:8px;color:#888;text-align:center", colspan=len(headers))] + ) + else: + rows = [] + for h in history: + action = _normalize_action_label(h.get("action")) + action_color = _ACTION_COLORS.get(action, "#f2f3f4") + succeeded = bool(h.get("succeeded", False)) + latency_ms = float(h.get("latency_ms", 0.0) or 0.0) + sla_ms = float(h.get("sla_ceiling_ms", 500.0) or 500.0) + breach = latency_ms > sla_ms + health_a = float(h.get("health_a", 0.0) or 0.0) + health_b = float(h.get("health_b", 0.0) or 0.0) + health_c = float(h.get("health_c", 0.0) or 0.0) + if action == "shed_load": + ok_cell = "—" + sla_cell = "—" + else: + ok_cell = "✅" if succeeded else "❌" + sla_cell = "❌" if breach else "✅" + rows.append( + _tr( + [ + _td(str(h.get("step", "—")), step_style), + _td(action, f"{action_style};background:{action_color}"), + _td(f"{health_a:.2f}", health_style), + _td(f"{health_b:.2f}", health_style), + _td(f"{health_c:.2f}", health_style), + _td(ok_cell, cell_style), + _td(sla_cell, cell_style), + _td(f"{latency_ms:.0f}", cell_style), + _td(f"{float(h.get('budget', 0.0) or 0.0):.2f}", cell_style), + _td(f"{float(h.get('reward', 0.0) or 0.0):+.3f}", cell_style), + ] + ) + ) + body = "".join(rows) + return f"{colgroup}{head}{body}
" + + +def _kpi_grid(items: List[Tuple[str, str]]) -> str: + cards = [] + for label, value in items: + cards.append( + "
" + f"
{html.escape(str(label))}
" + f"
{html.escape(str(value))}
" + "
" + ) + return "
" + "".join(cards) + "
" + + +def render_incident_timeline(scenario_name: str) -> str: + config = _task_config(scenario_name) + start_raw = getattr(config, "degradation_start_step", 999) + start = 999 if start_raw is None else int(start_raw) + target = str(getattr(config, "degradation_target", "A") or "A") + secondary_start = getattr(config, "secondary_degradation_start_step", 999) + secondary_target = str(getattr(config, "secondary_degradation_target", "") or "") + + items: List[str] = [] + if start < 999: + items.append(f"
Step {start}: degradation starts for Provider {target}
") + if secondary_target and secondary_start is not None and int(secondary_start) < 999: + items.append( + f"
Step {int(secondary_start)}: degradation starts for Provider {secondary_target}
" + ) + if not items: + return "
No configured incidents for this scenario.
" + return "
" + "".join(items) + "
" + + +def _policy_label(name: Optional[str], fallback: str) -> str: + text = str(name or "").strip() + return text if text else fallback + + +def _annotation_offsets(last_left: Optional[float], last_right: Optional[float], threshold: float = 0.03) -> Tuple[int, int]: + if last_left is None or last_right is None: + return (0, 0) + if abs(last_left - last_right) < threshold: + # Keep visual ordering consistent with actual values: + # higher final score label stays above lower final score label. + if last_left >= last_right: + return (10, -10) + return (-10, 10) + return (0, 0) + + +def render_grader_plot( + left_hist: List[Dict], + right_hist: List[Dict], + left_name: Optional[str] = None, + right_name: Optional[str] = None, +): + try: + import plotly.graph_objects as go + except Exception: + go = None + + if go is not None: + def series(hist: List[Dict]) -> List[float]: + out: List[float] = [] + for i in range(1, len(hist) + 1): + out.append(float(compute_grade(hist[:i]).get("overall_score", 0.0))) + return out + + y1 = series(left_hist) + y2 = series(right_hist) + x1 = list(range(1, len(y1) + 1)) + x2 = list(range(1, len(y2) + 1)) + + color_a = "#f39c12" + color_b = "#3498db" + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + yshift_a, yshift_b = _annotation_offsets(y1[-1] if y1 else None, y2[-1] if y2 else None) + + fig = go.Figure() + if y1: + fig.add_trace( + go.Scatter( + x=x1, + y=y1, + mode="lines", + name=label_a, + line=dict(color=color_a, width=3), + hovertemplate="Step %{x}
Benchmark Score %{y:.4%}", + ) + ) + fig.add_annotation( + x=x1[-1], + y=y1[-1], + text=_format_percent_precise(float(y1[-1])), + showarrow=False, + xanchor="left", + xshift=8, + yshift=yshift_a, + font=dict(color=color_a, size=12), + ) + if y2: + fig.add_trace( + go.Scatter( + x=x2, + y=y2, + mode="lines", + name=label_b, + line=dict(color=color_b, width=3), + hovertemplate="Step %{x}
Benchmark Score %{y:.4%}", + ) + ) + fig.add_annotation( + x=x2[-1], + y=y2[-1], + text=_format_percent_precise(float(y2[-1])), + showarrow=False, + xanchor="left", + xshift=8, + yshift=yshift_b, + font=dict(color=color_b, size=12), + ) + + fig.update_layout( + template="plotly_white", + title=dict(text=f"{MISSION_SCORE_LABEL} over steps", x=0.0, xanchor="left"), + margin=dict(l=50, r=20, t=45, b=40), + height=320, + hovermode="x unified", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0), + font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"), + ) + fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False) + fig.update_yaxes( + title_text=MISSION_SCORE_LABEL, + tickformat=",.2%", + range=[0, 1], + showgrid=True, + gridcolor="rgba(17,24,39,0.08)", + zeroline=False, + ) + return fig + + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.ticker import PercentFormatter + except Exception: + return None + + def series(hist: List[Dict]) -> List[float]: + out: List[float] = [] + for i in range(1, len(hist) + 1): + out.append(float(compute_grade(hist[:i]).get("overall_score", 0.0))) + return out + + y1 = series(left_hist) + y2 = series(right_hist) + x1 = list(range(1, len(y1) + 1)) + x2 = list(range(1, len(y2) + 1)) + + fig = plt.figure(figsize=(8, 3.2), dpi=120) + ax = fig.add_subplot(111) + + color_a = "#f39c12" + color_b = "#3498db" + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + yoffset_a, yoffset_b = _annotation_offsets(y1[-1] if y1 else None, y2[-1] if y2 else None) + if y1: + ax.plot(x1, y1, label=label_a, linewidth=2.2, color=color_a) + ax.annotate( + _format_percent_precise(float(y1[-1])), + xy=(x1[-1], y1[-1]), + xytext=(6, yoffset_a), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color=color_a, + ) + if y2: + ax.plot(x2, y2, label=label_b, linewidth=2.2, color=color_b) + ax.annotate( + _format_percent_precise(float(y2[-1])), + xy=(x2[-1], y2[-1]), + xytext=(6, yoffset_b), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color=color_b, + ) + + ax.set_ylim(0, 1) + ax.yaxis.set_major_formatter(PercentFormatter(xmax=1.0, decimals=2)) + ax.set_xlabel("Step") + ax.set_ylabel(MISSION_SCORE_LABEL) + ax.set_title(f"{MISSION_SCORE_LABEL} over steps", loc="left", fontsize=11, fontweight="bold", color="#111827") + fig.patch.set_facecolor("#ffffff") + ax.set_facecolor("#ffffff") + ax.grid(True, axis="y", alpha=0.18, linewidth=0.8) + ax.grid(False, axis="x") + for spine in ("top", "right"): + ax.spines[spine].set_visible(False) + for spine in ("left", "bottom"): + ax.spines[spine].set_color("#e5e7eb") + ax.tick_params(colors="#374151", labelsize=9) + ax.legend(loc="upper left", frameon=False, fontsize=9) + fig.tight_layout() + return fig + + +def render_reward_plot( + left_hist: List[Dict], + right_hist: List[Dict], + left_name: Optional[str] = None, + right_name: Optional[str] = None, +): + try: + import plotly.graph_objects as go + except Exception: + go = None + + def series(hist: List[Dict]) -> Tuple[List[int], List[float]]: + xs: List[int] = [] + ys: List[float] = [] + total = 0.0 + for i, h in enumerate(hist, start=1): + total += float(h.get("reward", 0.0) or 0.0) + xs.append(i) + ys.append(total) + return xs, ys + + x1, y1 = series(left_hist) + x2, y2 = series(right_hist) + if not y1 and not y2: + return None + + if go is not None: + color_a = "#f39c12" + color_b = "#3498db" + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + fig = go.Figure() + + if y1: + fig.add_trace( + go.Scatter( + x=x1, + y=y1, + mode="lines", + name=label_a, + line=dict(color=color_a, width=3), + hovertemplate="Step %{x}
Cumulative %{y:+.2f}", + ) + ) + fig.add_annotation( + x=x1[-1], + y=y1[-1], + text=f"{y1[-1]:+.2f}", + showarrow=False, + xanchor="left", + xshift=8, + font=dict(color=color_a, size=12), + ) + + if y2: + fig.add_trace( + go.Scatter( + x=x2, + y=y2, + mode="lines", + name=label_b, + line=dict(color=color_b, width=3), + hovertemplate="Step %{x}
Cumulative %{y:+.2f}", + ) + ) + fig.add_annotation( + x=x2[-1], + y=y2[-1], + text=f"{y2[-1]:+.2f}", + showarrow=False, + xanchor="left", + xshift=8, + font=dict(color=color_b, size=12), + ) + + fig.update_layout( + template="plotly_white", + title=dict(text="Cumulative reward over steps", x=0.0, xanchor="left"), + margin=dict(l=60, r=20, t=45, b=40), + height=280, + hovermode="x unified", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0), + font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"), + ) + fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False) + fig.update_yaxes( + title_text="Cumulative reward", + showgrid=True, + gridcolor="rgba(17,24,39,0.08)", + zeroline=False, + ) + return fig + + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + except Exception: + return None + + fig = plt.figure(figsize=(8, 2.8), dpi=120) + ax = fig.add_subplot(111) + + fig.patch.set_facecolor("#ffffff") + ax.set_facecolor("#ffffff") + ax.grid(True, axis="y", alpha=0.18, linewidth=0.8) + ax.grid(False, axis="x") + for spine in ("top", "right"): + ax.spines[spine].set_visible(False) + for spine in ("left", "bottom"): + ax.spines[spine].set_color("#e5e7eb") + ax.tick_params(colors="#374151", labelsize=9) + + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + if y1: + ax.plot(x1, y1, label=label_a, linewidth=2.2, color="#f39c12") + ax.annotate( + f"{y1[-1]:+.2f}", + xy=(x1[-1], y1[-1]), + xytext=(6, 0), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color="#f39c12", + ) + if y2: + ax.plot(x2, y2, label=label_b, linewidth=2.2, color="#3498db") + ax.annotate( + f"{y2[-1]:+.2f}", + xy=(x2[-1], y2[-1]), + xytext=(6, 0), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color="#3498db", + ) + + ax.set_xlabel("Step") + ax.set_ylabel("Cumulative reward") + ax.set_title("Cumulative reward over steps", loc="left", fontsize=11, fontweight="bold", color="#111827") + ax.legend(loc="upper left", frameon=False, fontsize=9) + fig.tight_layout() + return fig + + +def render_budget_consumed_plot( + left_hist: List[Dict], + right_hist: List[Dict], + left_name: Optional[str] = None, + right_name: Optional[str] = None, +): + try: + import plotly.graph_objects as go + except Exception: + go = None + + if go is not None: + def series(hist: List[Dict]) -> List[float]: + if not hist: + return [] + initial = float(hist[0].get("initial_budget", 0.0) or 0.0) + consumed: List[float] = [] + total = 0.0 + for h in hist: + total += float(h.get("cost", 0.0) or 0.0) + if initial > 0: + consumed.append(min(initial, total)) + else: + consumed.append(total) + return consumed + + y1 = series(left_hist) + y2 = series(right_hist) + if not y1 and not y2: + return None + + x1 = list(range(1, len(y1) + 1)) + x2 = list(range(1, len(y2) + 1)) + + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + fig = go.Figure() + if y1: + fig.add_trace( + go.Scatter( + x=x1, + y=y1, + mode="lines", + name=label_a, + line=dict(color="#f39c12", width=3), + hovertemplate="Step %{x}
Consumed $%{y:,.0f}", + ) + ) + fig.add_annotation( + x=x1[-1], + y=y1[-1], + text=f"${y1[-1]:,.0f}", + showarrow=False, + xanchor="left", + xshift=8, + font=dict(color="#f39c12", size=12), + ) + if y2: + fig.add_trace( + go.Scatter( + x=x2, + y=y2, + mode="lines", + name=label_b, + line=dict(color="#3498db", width=3), + hovertemplate="Step %{x}
Consumed $%{y:,.0f}", + ) + ) + fig.add_annotation( + x=x2[-1], + y=y2[-1], + text=f"${y2[-1]:,.0f}", + showarrow=False, + xanchor="left", + xshift=8, + font=dict(color="#3498db", size=12), + ) + + fig.update_layout( + template="plotly_white", + title=dict(text="Budget consumed over steps", x=0.0, xanchor="left"), + margin=dict(l=60, r=20, t=45, b=40), + height=280, + hovermode="x unified", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0), + font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"), + ) + fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False) + fig.update_yaxes( + title_text="Budget consumed ($)", + tickprefix="$", + separatethousands=True, + showgrid=True, + gridcolor="rgba(17,24,39,0.08)", + zeroline=False, + ) + return fig + + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.ticker import StrMethodFormatter + except Exception: + return None + + def series(hist: List[Dict]) -> List[float]: + if not hist: + return [] + initial = float(hist[0].get("initial_budget", 0.0) or 0.0) + consumed: List[float] = [] + total = 0.0 + for h in hist: + total += float(h.get("cost", 0.0) or 0.0) + if initial > 0: + consumed.append(min(initial, total)) + else: + consumed.append(total) + return consumed + + y1 = series(left_hist) + y2 = series(right_hist) + if not y1 and not y2: + return None + + x1 = list(range(1, len(y1) + 1)) + x2 = list(range(1, len(y2) + 1)) + + fig = plt.figure(figsize=(8, 2.8), dpi=120) + ax = fig.add_subplot(111) + + fig.patch.set_facecolor("#ffffff") + ax.set_facecolor("#ffffff") + + label_a = _policy_label(left_name, "Policy A") + label_b = _policy_label(right_name, "Policy B") + if y1: + ax.plot(x1, y1, label=label_a, linewidth=2.2, color="#f39c12") + ax.fill_between(x1, y1, alpha=0.10, color="#f39c12") + ax.annotate( + f"${y1[-1]:,.0f}", + xy=(x1[-1], y1[-1]), + xytext=(6, 0), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color="#f39c12", + ) + if y2: + ax.plot(x2, y2, label=label_b, linewidth=2.2, color="#3498db") + ax.fill_between(x2, y2, alpha=0.08, color="#3498db") + ax.annotate( + f"${y2[-1]:,.0f}", + xy=(x2[-1], y2[-1]), + xytext=(6, 0), + textcoords="offset points", + ha="left", + va="center", + fontsize=9, + fontweight=600, + color="#3498db", + ) + + ax.set_xlabel("Step") + ax.set_ylabel("Budget consumed ($)") + ax.set_title("Budget consumed over steps", loc="left", fontsize=11, fontweight="bold", color="#111827") + ax.yaxis.set_major_formatter(StrMethodFormatter("${x:,.0f}")) + ax.grid(True, axis="y", alpha=0.18, linewidth=0.8) + ax.grid(False, axis="x") + for spine in ("top", "right"): + ax.spines[spine].set_visible(False) + for spine in ("left", "bottom"): + ax.spines[spine].set_color("#e5e7eb") + ax.tick_params(colors="#374151", labelsize=9) + ax.legend(loc="upper left", frameon=False, fontsize=9) + fig.tight_layout() + return fig + + +def render_history_table(history: List[Dict]) -> str: + headers = ["Step", "Action", "Health A", "Health B", "Health C", "Budget", "Reward"] + head = _tr([_th(h) for h in headers], style=_HEADER_ROW_STYLE) + if not history: + body = _tr([ + _td("No steps yet.", "padding:8px;color:#888;text-align:center", colspan=len(headers)) + ]) + else: + rows = [] + for h in history: + action = h["action"] + action_color = _ACTION_COLORS.get(action, "#f2f3f4") + rows.append( + _tr( + [ + _td(str(h["step"]), _CELL_STYLE), + _td(action, f"{_ACTION_CELL_STYLE};background:{action_color}"), + _td(f"{h['health_a']:.2f}", _CELL_STYLE), + _td(f"{h['health_b']:.2f}", _CELL_STYLE), + _td(f"{h['health_c']:.2f}", _CELL_STYLE), + _td(f"{h['budget']:.2f}", _CELL_STYLE), + _td(f"{h['reward']:+.3f}", _CELL_STYLE), + ] + ) + ) + body = "".join(rows) + return _table(head, body) + + +def render_side_panel(side: Dict, run: Dict, scenario_name: str) -> Tuple[str, str, str, str, str, str, str, str]: + scenario_cfg = _task_config(scenario_name) + global_step = int(run.get("step", 0) or 0) + common = _common_provider_health(scenario_cfg, global_step) + + obs = side.get("obs", {}) or {} + history = side.get("history", []) or [] + last = history[-1] if history else None + + grade = compute_grade(history) if history else {} + adaptation = grade.get("adaptation_score") if history else None + latency = float(last.get("latency_ms", 0.0) or 0.0) if last else None + last_action = str(last.get("action")) if last else "—" + budget_val = float(obs.get("budget_remaining", 0.0) or 0.0) + reward_val = float(last.get("reward", 0.0) or 0.0) if last else None + + kpis = _kpi_grid( + [ + ("Step", f"{global_step} / {MAX_STEPS}"), + ("Last action", last_action), + ("Latency (ms)", (f"{latency:.0f}" if latency is not None else "—")), + ("Budget remaining", f"{budget_val:.2f}"), + ("Reward", ((f"{reward_val:+.3f}") if reward_val is not None else "—")), + ("Adaptation", ((f"{float(adaptation):.3f}") if adaptation is not None else "—")), + ] + ) + + metrics = _summary_metrics(history) + summary = _kpi_grid( + [ + ("Failed %", f"{metrics['failed_pct']:.1f}%"), + ("SLA breach %", f"{metrics['sla_breach_pct']:.1f}%"), + ("Avg latency (ms)", f"{metrics['avg_latency_ms']:.1f}"), + ] + ) + summary = summary + render_data_quality_panel(history) + + grade_html = _GRADER_PENDING() + if side.get("done") and history: + grade_html = render_grader(grade) + + return ( + str(side.get("status", "")), + render_common_providers(common) if global_step > 0 else _PROVIDER_EMPTY(), + render_budget(obs) if obs else "", + kpis, + _step_badges(last), + summary, + render_history_table_compare(history), + grade_html, + ) diff --git a/gradio_ui/state.py b/gradio_ui/state.py new file mode 100644 index 0000000000000000000000000000000000000000..1ba727de78af1deb2523f4094ec838af0a9d0561 --- /dev/null +++ b/gradio_ui/state.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional + +from budget_router.models import Observation + + +def fresh_side_state() -> Dict: + return { + "env": None, + "policy_name": "", + "policy_runner": None, + "obs": {}, + "history": [], + "cumulative_reward": 0.0, + "step": 0, + "done": False, + "status": "", + } + + +def _observation_to_dict(observation: Observation) -> Dict[str, Any]: + return { + "provider_a_status": float(observation.provider_a_status), + "provider_b_status": float(observation.provider_b_status), + "provider_c_status": float(observation.provider_c_status), + "budget_remaining": float(observation.budget_remaining), + "queue_backlog": float(observation.queue_backlog), + "system_latency": float(observation.system_latency), + "step_count": float(observation.step_count), + "done": bool(getattr(observation, "done", False)), + "reward": float(getattr(observation, "reward", 0.0) or 0.0), + "metadata": dict(getattr(observation, "metadata", {}) or {}), + } + + +def record_step( + step: int, + action: str, + obs: Dict, + reward: float, + meta: Dict, + health_obs: Optional[Dict] = None, +) -> Dict: + health = health_obs or obs + return { + "step": step, + "action": action, + "health_a": health.get("provider_a_status", 0), + "health_b": health.get("provider_b_status", 0), + "health_c": health.get("provider_c_status", 0), + "budget": obs.get("budget_remaining", 0), + "reward": reward, + "meta_raw": dict(meta or {}), + "succeeded": meta.get("request_succeeded", False), + "cost": meta.get("cost", 0.0), + "latency_ms": meta.get("latency_ms", 0.0), + "sla_ceiling_ms": meta.get("sla_ceiling_ms", 500.0), + "initial_budget": meta.get("initial_budget", 1.0), + "degradation_start_step": meta.get("degradation_start_step", 999), + "secondary_degradation_start_step": meta.get("secondary_degradation_start_step"), + } diff --git a/gradio_ui/theme.py b/gradio_ui/theme.py new file mode 100644 index 0000000000000000000000000000000000000000..3f3d8157688dcf05ae4d6b70ae155adf3554d3c0 --- /dev/null +++ b/gradio_ui/theme.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +import gradio as gr + +# Minimal CSS overrides - theme handles most styling via _dark variants +LIGHT_CSS = """ +/* Force light color scheme */ +:root, .dark { + color-scheme: light !important; + --background-fill-primary: #ffffff !important; + --background-fill-primary-dark: #ffffff !important; + --background-fill-secondary: #ffffff !important; + --background-fill-secondary-dark: #ffffff !important; +} + +/* Ensure all text is dark */ +.gradio-container label, +.gradio-container span { + color: #1f2937 !important; +} + +/* Radio/checkbox label pills - force white background */ +.gradio-container .wrap[data-testid=\"checkbox-group\"] label, +.gradio-container .wrap[data-testid=\"radio-group\"] label { + background: #ffffff !important; + border: 1px solid #e5e7eb !important; + color: #1f2937 !important; +} +.gradio-container .wrap[data-testid=\"checkbox-group\"] label.selected, +.gradio-container .wrap[data-testid=\"radio-group\"] label.selected { + background: #eef2ff !important; + border-color: #4f46e5 !important; +} +.gradio-container input, +.gradio-container textarea, +.gradio-container select { + color: #1f2937 !important; + background: #ffffff !important; +} + +.gradio-container [data-testid=\"dropdown\"], +.gradio-container [data-testid=\"dropdown\"] * { + background: #ffffff !important; + color: #1f2937 !important; +} + +/* Dropdown menu is sometimes portaled outside .gradio-container, so also target by role globally */ +[role=\"listbox\"] { + background: #ffffff !important; + color: #1f2937 !important; +} + +[role=\"option\"] { + background: #ffffff !important; + color: #1f2937 !important; +} + +[role=\"option\"]:hover, +[role=\"option\"][aria-selected=\"true\"], +[role=\"option\"][data-highlighted], +[role=\"option\"][data-selected], +[role=\"option\"][data-state=\"checked\"], +[role=\"option\"].selected { + background: #ffffff !important; + color: #1f2937 !important; +} + +/* KPI cards */ +.kpi-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + margin-top: 6px; +} + +.kpi-card { + border: 1px solid #e5e7eb; + background: #ffffff; + border-radius: 10px; + padding: 10px 12px; +} + +.kpi-label { + font-size: 12px; + color: #6b7280; + margin-bottom: 4px; +} + +.kpi-value { + font-size: 14px; + font-weight: 600; + color: #111827; + line-height: 1.2; + font-variant-numeric: tabular-nums; +} + +/* Episode history tables: keep side-by-side clean and compact */ +.episode-history-row { + align-items: stretch; +} + +.episode-history-table { + overflow: hidden; +} + +.episode-history-table table { + width: 100% !important; + table-layout: fixed; +} + +.episode-history-table th { + white-space: normal; + overflow: visible; + text-overflow: clip; + line-height: 1.2; +} + +.episode-history-table td { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +""" + +THEME = gr.themes.Default( + primary_hue=gr.themes.colors.indigo, + secondary_hue=gr.themes.colors.indigo, + neutral_hue=gr.themes.colors.gray, +).set( + # Body + body_background_fill="#f7f8fb", + body_background_fill_dark="#f7f8fb", + body_text_color="#1f2937", + body_text_color_dark="#1f2937", + # Blocks + block_background_fill="#ffffff", + block_background_fill_dark="#ffffff", + block_border_color="#e5e7eb", + block_border_color_dark="#e5e7eb", + block_label_text_color="#1f2937", + block_label_text_color_dark="#1f2937", + block_title_text_color="#1f2937", + block_title_text_color_dark="#1f2937", + # Inputs + input_background_fill="#ffffff", + input_background_fill_dark="#ffffff", + input_border_color="#9ca3af", + input_border_color_dark="#9ca3af", + input_placeholder_color="#6b7280", + input_placeholder_color_dark="#6b7280", + # Buttons + button_primary_background_fill="#4f46e5", + button_primary_background_fill_dark="#4f46e5", + button_primary_text_color="#ffffff", + button_primary_text_color_dark="#ffffff", + button_secondary_background_fill="#4f46e5", + button_secondary_background_fill_dark="#4f46e5", + button_secondary_text_color="#ffffff", + button_secondary_text_color_dark="#ffffff", + button_secondary_background_fill_hover="#4338ca", + button_secondary_background_fill_hover_dark="#4338ca", + # Radio/Checkbox - THIS IS THE KEY FIX + checkbox_background_color="#ffffff", + checkbox_background_color_dark="#ffffff", + checkbox_border_color="#d1d5db", + checkbox_border_color_dark="#d1d5db", + checkbox_label_background_fill="#ffffff", + checkbox_label_background_fill_dark="#ffffff", + checkbox_label_background_fill_hover="#f3f4f6", + checkbox_label_background_fill_hover_dark="#f3f4f6", + checkbox_label_background_fill_selected="#eef2ff", + checkbox_label_background_fill_selected_dark="#eef2ff", + checkbox_label_border_color="#e5e7eb", + checkbox_label_border_color_dark="#e5e7eb", + checkbox_label_text_color="#1f2937", + checkbox_label_text_color_dark="#1f2937", +) diff --git a/inference.py b/inference.py new file mode 100644 index 0000000000000000000000000000000000000000..c7d941699879fcb800ffe8df7775f48c9478bd45 --- /dev/null +++ b/inference.py @@ -0,0 +1,538 @@ +import json +import os +from pathlib import Path +from typing import Any, Callable, Dict, Iterable, List, Literal + +import typer +from openai import OpenAI + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation, TaskConfig +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import episode_metrics, grade_episode +from budget_router.tasks import EASY, HARD, HARD_MULTI, MEDIUM + +_VALID_ACTIONS = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] + + +def _parse_llm_action(response_text: str) -> str: + """Extract a valid action from LLM output. Falls back to shed_load — never raises.""" + text = response_text.strip().lower() + for action in _VALID_ACTIONS: + if action in text: + return action + return "shed_load" # safe fallback: always valid, never crashes + + +SYSTEM_PROMPT = """ +You are a cost-aware LLM API routing agent managing a production system. +At each step, output EXACTLY ONE action string. Nothing else. + +ENVIRONMENT: + Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable). + provider_X_status = windowed success rate [0=always fails, 1=always succeeds]. + IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to + in this episode — it is unobserved, not confirmed healthy. Route to it once to get + a real reading. Do not treat 0.500 as a health signal. + budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty. + step_count [0→1], steps_remaining: episode progress (20 steps total). + +VALID ACTIONS (output ONLY one): + route_to_a | route_to_b | route_to_c | shed_load + +GOLDEN RULE — DEFAULT STRATEGY: + Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score. + +NOISE CALIBRATION (critical): +- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals. +- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps +indicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise. +- REAL degradation signal: sustained negative trend AND current status is visibly declining. +- Only when both conditions hold across consecutive observations should you consider early switching. +- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit. + + +WHEN TO SWITCH (use your conversation history): +A → B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable, + OR status_a is already below 0.52 (failure probability exceeds success probability). +B → C: Same principle — sustained decline signals, not single-step noise. +Never switch based on a single bad observation — noise causes occasional dips. + +BUDGET RUNWAY — HARD CONSTRAINT: +budget_runway_at_current_rate shows how many more steps you can afford at current spend rate. +If budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY. +If budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are + both below 0.30 status. Prefer shed_load over routing C when budget is this low. +NEVER route to any provider if doing so would leave budget_remaining below the cost of +that provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode +value accumulated so far — budget survival is non-negotiable. +TASK PROFILES (the task name appears in each observation — use it): + easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative. + medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails. + hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences. + Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10). + Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy. + CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion. + +Output only the action string.""" + +OBJECTIVE_FEEDBACK_PROMPT = """ +You are a budget-aware API routing agent managing a production system. +At each step, output EXACTLY ONE action string and nothing else. + +VALID ACTIONS: +route_to_a | route_to_b | route_to_c | shed_load + +ENVIRONMENT INTUITION: +You route one request per step across three providers: +- A: cheapest ($0.01), useful for conserving budget, but may degrade. +- B: medium cost ($0.05), usually the bridge provider. +- C: expensive ($0.10), most reliable, but using it too early can bankrupt the episode. +- shed_load: reject this request; use only when routing is likely worse than abstaining or budget is critically unsafe. + +OBSERVATIONS: +provider_X_status is a rolling recent success estimate, not true health. +- Exactly 0.500 means unobserved in this episode: no evidence yet. +- After one probe, the status may jump to 0.000 or 1.000; that is weak evidence because it may be only one sample. +- Repeated outcomes, sustained negative trend, worsening reward, or repeated failures are stronger evidence. +Do not treat a single success as proof of health or a single failure as proof of degradation. + +PRIMARY OBJECTIVE: +Maximize full-episode grader score: +- successful routed requests +- low latency and SLA health +- budget preservation +- adaptation after provider degradation + +DECISION POLICY: +1. Budget survival is mandatory. Avoid the -10 budget exhaustion cliff. +2. Prefer the cheapest provider that is plausibly healthy, but do not blindly follow a fixed threshold. +3. Probe unknown providers when information is valuable and affordable. +4. Switch away from a provider when there is repeated failure, sustained decline, or clearly bad status. +5. Use C as late-phase reliability capacity, not as the default. +6. Prefer shed_load when all available routed choices look likely to fail or when routing would risk budget exhaustion. + +TASK-SPECIFIC STRATEGY: +easy: +- Stable task. Prefer cheap routing. Do not overreact to noise. + +medium: +- A may degrade after early steps. Start cheap, then move to B when A shows repeated weakness. + +hard: +- A may degrade from the beginning. Probe cheaply, but react faster to repeated A failures or sustained decline. + +hard_multi: +- This is a sequential cascade: A degrades early; B can degrade later. +- Early phase: conserve budget. Use/probe A only while evidence supports it. +- Middle phase: B is often the bridge provider after A weakens. +- Late phase: preserve enough runway for C if B begins failing. +- Do not burn C too early; do not stay on A/B after repeated failures. + +BUDGET RUNWAY: +If budget_runway_at_current_rate < steps_remaining, current spending is unsafe. +If budget_remaining < 0.15, treat C as emergency-only unless A and B both look very poor. +If routing risks budget exhaustion, shed_load is better than bankruptcy. + +Use previous_step_feedback when present: +- previous_success=false, negative reward, high latency, or repeated same-provider failures are evidence to update your belief. +- previous_success=true is useful evidence but not proof, especially after only one sample. + +Output only one valid action string. +""" + +app = typer.Typer(add_completion=False) + +API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY") + +API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1" +MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct" +LLM_TIMEOUT_SECONDS = float(os.getenv("LLM_TIMEOUT_SECONDS") or "25") +LLM_MAX_RETRIES = int(os.getenv("LLM_MAX_RETRIES") or "1") +LLM_LOG_RAW = (os.getenv("LLM_LOG_RAW") or "").strip().lower() in {"1", "true", "yes", "y", "on"} +LLM_LOG_RAW_MAX_CHARS = int(os.getenv("LLM_LOG_RAW_MAX_CHARS") or "220") +LLM_POLICY_MODE = (os.getenv("LLM_POLICY_MODE") or "baseline").strip().lower() +BENCHMARK_NAME = os.getenv("BENCHMARK_NAME") or "budget_router" + +SEED_SETS: Dict[str, List[int]] = { + "development": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "heldout": [100, 101, 102, 103, 104, 105, 106, 107, 108, 109], +} +TASKS: Dict[str, TaskConfig] = { + "easy": EASY, + "medium": MEDIUM, + "hard": HARD, + "hard_multi": HARD_MULTI, +} +VALID_ACTIONS = [action.value for action in ActionType] + + +class LLMRouter: + def __init__( + self, + api_base_url: str, + model_name: str, + api_key: str, + prompt_mode: str | None = None, + ) -> None: + self._client = OpenAI( + base_url=api_base_url, + api_key=api_key, + timeout=LLM_TIMEOUT_SECONDS, + max_retries=LLM_MAX_RETRIES, + ) + self._model_name = model_name + self._prompt_mode = (prompt_mode or LLM_POLICY_MODE or "baseline").strip().lower() + self._messages: List[Dict[str, str]] = [] + self.last_error: str | None = None + self.last_raw_output: str | None = None + self.last_parsed_action: str | None = None + self._prev_obs: dict | None = None + self._prev2_obs: dict | None = None + self._task_name: str = "" + self.reset() + + def reset(self, task_name: str = "") -> None: + prompt = OBJECTIVE_FEEDBACK_PROMPT if self._prompt_mode == "objective_feedback" else SYSTEM_PROMPT + self._messages = [{"role": "system", "content": prompt}] + self.last_error = None + self.last_raw_output = None + self.last_parsed_action = None + self._prev_obs = None + self._prev2_obs = None + self._task_name = task_name + + def choose_action(self, observation: Observation) -> Action: + obs = observation + if not self._messages: + self.reset(task_name=self._task_name) + elif obs.step_count == 0.0 and len(self._messages) > 1: + self.reset(task_name=self._task_name) + + # ── Compute 2-step trend (more noise-robust than single-step delta) ── + trend_text = "" + budget_runway_text = "" + + if self._prev2_obs is not None: + # Average per-step change over 2 steps — variance is ~30% lower than 1-step delta + ta = (obs.provider_a_status - self._prev2_obs["a"]) / 2.0 + tb = (obs.provider_b_status - self._prev2_obs["b"]) / 2.0 + tc = (obs.provider_c_status - self._prev2_obs["c"]) / 2.0 + trend_text = f"\ntrend (avg/step, 2-step): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + elif self._prev_obs is not None: + # First step — single-step delta only, label as less reliable + ta = obs.provider_a_status - self._prev_obs["a"] + tb = obs.provider_b_status - self._prev_obs["b"] + tc = obs.provider_c_status - self._prev_obs["c"] + trend_text = f"\ntrend (1-step only, noisy): A:{ta:+.3f} B:{tb:+.3f} C:{tc:+.3f}" + + if self._prev_obs is not None: + budget_spent = self._prev_obs["budget"] - obs.budget_remaining + if budget_spent > 0.001: + runway = int(obs.budget_remaining / budget_spent) + budget_runway_text = f"\nbudget_runway_at_current_rate: ~{runway} steps" + else: + budget_runway_text = "\nbudget_runway_at_current_rate: >20 steps" + + steps_total = 20 + steps_remaining = max(1, steps_total - int(round(obs.step_count * steps_total))) + + task_line = f"\ntask: {self._task_name}" if self._task_name else "" + obs_text = "\n".join([ + f"provider_a_status: {obs.provider_a_status:.3f}", + f"provider_b_status: {obs.provider_b_status:.3f}", + f"provider_c_status: {obs.provider_c_status:.3f}", + f"budget_remaining: {obs.budget_remaining:.3f}", + f"queue_backlog: {obs.queue_backlog:.3f}", + f"system_latency: {obs.system_latency:.3f}", + f"step_count: {obs.step_count:.3f}", + f"steps_remaining: {steps_remaining}", + ]) + obs_text += trend_text + budget_runway_text + task_line + if self._prompt_mode == "objective_feedback": + feedback_lines = self._previous_step_feedback(observation=obs) + if feedback_lines: + obs_text += "\n" + feedback_lines + user_prompt = f"Current observation:\n{obs_text}\n\nYour action:" + + # Shift history: prev becomes prev2, current becomes prev + self._prev2_obs = self._prev_obs + self._prev_obs = { + "a": obs.provider_a_status, + "b": obs.provider_b_status, + "c": obs.provider_c_status, + "budget": obs.budget_remaining, + } + + client = self._client + model_name = self._model_name + self._messages.append({"role": "user", "content": user_prompt}) + try: + response = client.with_options(timeout=LLM_TIMEOUT_SECONDS).chat.completions.create( + model=model_name, + messages=self._messages, + max_tokens=30, + temperature=0, + ) + raw = response.choices[0].message.content or "" + action_str = _parse_llm_action(raw) + action_str = self._apply_budget_safety_guard(action_str=action_str, observation=obs) + self.last_raw_output = raw + self.last_parsed_action = action_str + self.last_error = None + except Exception as e: + self.last_error = str(e) + action_str = "shed_load" + self.last_raw_output = None + self.last_parsed_action = action_str + self._messages.append({"role": "assistant", "content": action_str}) + return Action(action_type=ActionType(action_str)) + + def _apply_budget_safety_guard(self, action_str: str, observation: Observation) -> str: + """Prevent only actions that would immediately exhaust the public remaining budget.""" + if action_str == "shed_load": + return action_str + + scenario = TASKS.get(self._task_name) + if scenario is None: + return action_str + + action_costs = { + "route_to_a": scenario.cost_a, + "route_to_b": scenario.cost_b, + "route_to_c": scenario.cost_c, + } + selected_cost = action_costs.get(action_str, 0.0) + budget_dollars = float(observation.budget_remaining) * float(scenario.initial_budget) + + if selected_cost >= budget_dollars - 1e-9: + return "shed_load" + return action_str + + def _previous_step_feedback(self, observation: Observation) -> str: + metadata = getattr(observation, "metadata", None) or {} + if not metadata: + return "" + + previous_action = metadata.get("action_type") + if not previous_action: + return "" + + reward = observation.reward + latency = metadata.get("latency_ms") + cost = metadata.get("cost") + succeeded = metadata.get("request_succeeded") + budget_exhausted = metadata.get("budget_exhausted", False) + + feedback_parts = [ + "previous_step_feedback:", + f" previous_action: {previous_action}", + ] + if reward is not None: + feedback_parts.append(f" previous_reward: {float(reward):+.2f}") + if succeeded is not None: + feedback_parts.append(f" previous_success: {str(bool(succeeded)).lower()}") + if cost is not None: + feedback_parts.append(f" previous_cost: {float(cost):.2f}") + if latency is not None: + feedback_parts.append(f" previous_latency_ms: {float(latency):.2f}") + if budget_exhausted: + feedback_parts.append(" previous_budget_exhausted: true") + return "\n".join(feedback_parts) + + +def _single_line(value: str | None) -> str: + if not value: + return "null" + return str(value).replace("\n", " ").replace("\r", " ") + + +def _truncate_and_sanitize(value: str | None, max_chars: int) -> str: + if not value: + return "null" + s = _single_line(value).strip() + if len(s) <= max_chars: + return s + return s[: max(0, max_chars - 3)] + "..." + + +def _observation_payload(observation: Observation) -> Dict[str, float]: + return { + "provider_a_status": float(observation.provider_a_status), + "provider_b_status": float(observation.provider_b_status), + "provider_c_status": float(observation.provider_c_status), + "budget_remaining": float(observation.budget_remaining), + "queue_backlog": float(observation.queue_backlog), + "system_latency": float(observation.system_latency), + "step_count": float(observation.step_count), + } + + +def _reported_score(value: float) -> float: + return min(max(float(value), 0.001), 0.999) + + +def log_start(task: str, env: str, model: str) -> None: + print(f"[START] task={task} env={env} model={model}", flush=True) + + +def log_step( + step: int, + action: str, + reward: float, + done: bool, + error: str | None, + llm_raw: str | None = None, + llm_parsed: str | None = None, +) -> None: + base = ( + f"[STEP] step={step} action={action} reward={reward:.2f} done={str(done).lower()} " + f"error={_single_line(error)}" + ) + if LLM_LOG_RAW: + raw_s = _truncate_and_sanitize(llm_raw, max_chars=max(20, LLM_LOG_RAW_MAX_CHARS)) + parsed_s = _single_line(llm_parsed) + base += f" llm_raw={raw_s} llm_parsed={parsed_s}" + print(base, flush=True) + + +def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None: + rewards_str = ",".join(f"{reward:.2f}" for reward in rewards) + print( + f"[END] success={str(success).lower()} steps={steps} score={_reported_score(score):.3f} rewards={rewards_str}", + flush=True, + ) + + +def select_policy(policy_name: Literal["heuristic", "llm"]) -> object: + if policy_name == "heuristic": + return heuristic_baseline_policy + + if not API_KEY or not API_BASE_URL: + raise RuntimeError( + "LLM policy requires API_BASE_URL and API_KEY and reads MODEL_NAME from environment variables." + ) + return LLMRouter(api_base_url=API_BASE_URL, model_name=MODEL_NAME, api_key=API_KEY) + + +def choose_action(policy_name: Literal["heuristic", "llm"], policy: object, observation: Observation) -> Action: + if policy_name == "heuristic": + return policy(observation) + return policy.choose_action(observation) + + +def run_episode( + env: BudgetRouterEnv, + scenario: TaskConfig, + seed: int, + episode: int, + policy_name: Literal["heuristic", "llm"], + policy: object, +) -> Dict[str, Any]: + total_reward = 0.0 + grader_score: float | None = None + rewards: List[float] = [] + steps_taken = 0 + success = False + + if policy_name == "llm": + policy.reset(task_name=scenario.name) + + log_start(task=scenario.name, env=BENCHMARK_NAME, model=MODEL_NAME) + + try: + observation = env.reset(seed=seed, scenario=scenario) + while not observation.done: + action = choose_action(policy_name=policy_name, policy=policy, observation=observation) + action_name = action.action_type.value + observation = env.step(action) + reward = float(observation.reward or 0.0) + total_reward += reward + rewards.append(reward) + steps_taken = env._internal.current_step + step_error = getattr(policy, "last_error", None) if policy_name == "llm" else None + llm_raw = getattr(policy, "last_raw_output", None) if policy_name == "llm" else None + llm_parsed = getattr(policy, "last_parsed_action", None) if policy_name == "llm" else None + log_step( + step=env._internal.current_step, + action=action_name, + reward=reward, + done=bool(observation.done), + error=step_error, + llm_raw=llm_raw, + llm_parsed=llm_parsed, + ) + + metrics = episode_metrics(env._internal.history) + metrics["seed"] = seed + metrics["episode"] = episode + metrics["total_reward"] = round(total_reward, 4) + metrics["episode_length"] = env._internal.current_step + grader = grade_episode(env._internal.history) + grader_score = float(grader["overall_score"]) + success = grader_score > 0.0 + metrics["grader_score"] = grader_score + metrics["grader_breakdown"] = grader + return metrics + finally: + close_fn = getattr(env, "close", None) + if callable(close_fn): + close_fn() + if grader_score is None: + grader_score = float(grade_episode(env._internal.history)["overall_score"]) + success = grader_score > 0.0 + log_end(success=success, steps=steps_taken, score=grader_score, rewards=rewards) + + +def summarize(metrics: Iterable[Dict[str, float]]) -> Dict[str, float]: + rows = list(metrics) + return { + "mean_reward": round(sum(row["total_reward"] for row in rows) / len(rows), 4), + "mean_success_rate": round(sum(row["success_rate"] for row in rows) / len(rows), 4), + "mean_cost": round(sum(row["total_cost_spent"] for row in rows) / len(rows), 4), + "mean_latency_ms": round(sum(row["average_latency_ms"] for row in rows) / len(rows), 2), + "mean_grader_score": round(sum(row["grader_score"] for row in rows) / len(rows), 4), + } + + +@app.command() +def main( + policy: Literal["heuristic", "llm"] = typer.Option("llm" if API_KEY and API_BASE_URL else "heuristic"), + seed_set: Literal["development", "heldout"] = typer.Option("development"), + scenario: Literal["all", "easy", "medium", "hard", "hard_multi"] = typer.Option("all"), + max_seeds: int = typer.Option(1), + output_path: Path = typer.Option(Path("baseline_results.json")), +) -> None: + selected_policy = select_policy(policy) + selected_tasks = TASKS if scenario == "all" else {scenario: TASKS[scenario]} + selected_seeds = SEED_SETS[seed_set][: max(1, max_seeds)] + results: Dict[str, Dict[str, object]] = {} + episode = 1 + + for task_name, task_config in selected_tasks.items(): + task_metrics = [] + for seed in selected_seeds: + env = BudgetRouterEnv() + task_metrics.append( + run_episode( + env=env, + scenario=task_config, + seed=seed, + episode=episode, + policy_name=policy, + policy=selected_policy, + ) + ) + episode += 1 + results[task_name] = { + "policy": policy, + "seed_set": seed_set, + "summary": summarize(task_metrics), + "episodes": task_metrics, + } + + output_path.write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8") + + +if __name__ == "__main__": + app() diff --git a/models.py b/models.py new file mode 100644 index 0000000000000000000000000000000000000000..d95728f18f55bc921ff4cccd56119534ac046460 --- /dev/null +++ b/models.py @@ -0,0 +1,3 @@ +from budget_router.models import Action, ActionType, EnvState, Observation, TaskConfig + +__all__ = ["Action", "ActionType", "EnvState", "Observation", "TaskConfig"] diff --git a/openenv.yaml b/openenv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f72d02ac4f8a813550735cf27cd575ce0f3c4d3 --- /dev/null +++ b/openenv.yaml @@ -0,0 +1,20 @@ +spec_version: 1 +name: budget_router +type: space +runtime: fastapi +app: server.app:app +port: 8000 +display_name: "Budget Router — Budgeted API Reliability RL Environment" +description: > + A POMDP environment where an agent routes requests across three providers + under budget, SLA, and reliability constraints. Providers degrade non-stationarily; + the agent must detect degradation from windowed observations and adapt routing. + Four difficulty tiers: easy (stable), medium (A degrades after step 5), + hard (immediate degradation, tight budget), hard_multi (cascade failure A+B). + Designed for post-training agentic reliability agents. +tags: + - reliability + - api-routing + - pomdp + - non-stationary + - budgeted-rl diff --git a/outputs/eval_ppo_hard_multi_20260408.txt b/outputs/eval_ppo_hard_multi_20260408.txt new file mode 100644 index 0000000000000000000000000000000000000000..c415ae0999801b43d71f065393d27bd45b43f3b6 --- /dev/null +++ b/outputs/eval_ppo_hard_multi_20260408.txt @@ -0,0 +1,44 @@ +[eval] Loading trained_models/ppo_hard_multi_100k.zip + +─── PPO agent (deterministic, Hard_Multi) ─── + [PPO] seed= 0 overall=0.6554 adapt=0.8889 budget=0.0909 success=0.6500 + [PPO] seed= 1 overall=0.6866 adapt=0.9444 budget=0.0909 success=0.7000 + [PPO] seed= 2 overall=0.7255 adapt=1.0000 budget=0.0909 success=0.7500 + [PPO] seed= 3 overall=0.6833 adapt=0.9444 budget=0.0909 success=0.7000 + [PPO] seed= 4 overall=0.7247 adapt=1.0000 budget=0.0909 success=0.7500 + [PPO] seed= 5 overall=0.6472 adapt=0.8889 budget=0.0909 success=0.6500 + [PPO] seed= 6 overall=0.7317 adapt=1.0000 budget=0.0909 success=0.7500 + [PPO] seed= 7 overall=0.6417 adapt=0.8889 budget=0.1364 success=0.6000 + [PPO] seed= 8 overall=0.6840 adapt=0.9444 budget=0.0909 success=0.7000 + [PPO] seed= 9 overall=0.7222 adapt=1.0000 budget=0.0909 success=0.7500 + +─── Heuristic baseline (Hard_Multi) ─── + [HEU] seed= 0 overall=0.5987 adapt=0.8333 budget=0.0545 success=0.6000 + [HEU] seed= 1 overall=0.6235 adapt=0.7778 budget=0.0364 success=0.7000 + [HEU] seed= 2 overall=0.6609 adapt=0.7778 budget=0.0727 success=0.7500 + [HEU] seed= 3 overall=0.5782 adapt=0.6278 budget=0.1273 success=0.6500 + [HEU] seed= 4 overall=0.6139 adapt=0.6833 budget=0.0818 success=0.7000 + [HEU] seed= 5 overall=0.5898 adapt=0.9444 budget=0.0364 success=0.5000 + [HEU] seed= 6 overall=0.6526 adapt=0.7778 budget=0.0273 success=0.7500 + [HEU] seed= 7 overall=0.5994 adapt=0.6833 budget=0.1545 success=0.7000 + [HEU] seed= 8 overall=0.5893 adapt=0.6667 budget=0.1000 success=0.6500 + [HEU] seed= 9 overall=0.5881 adapt=0.5833 budget=0.3000 success=0.6000 + +══════════════════════════════════════════════════════════ + HARD_MULTI: PPO vs Heuristic — Statistical Report +══════════════════════════════════════════════════════════ + + PPO grader: 0.6902 ± 0.0345 95% CI [0.6656, 0.7149] + HEU grader: 0.6094 ± 0.0282 95% CI [0.5893, 0.6296] + Delta: +0.0808 (+13.3%) + Win rate: 100% (10/10 seeds PPO wins) + + ── Sub-score breakdown ── + Adaptation: PPO=0.9500 HEU=0.7356 Δ=+0.2144 + Budget: PPO=0.0954 HEU=0.0991 Δ=-0.0036 + + VERDICT: ✅ STRONG: PPO 95% CI is entirely above heuristic 95% CI — non-overlapping. +══════════════════════════════════════════════════════════ + + README snippet (paste into benchmark table): + Hard_Multi PPO row: 0.6902 (Δ +13.3% vs heuristic) diff --git a/outputs/eval_results_20260408_103950.json b/outputs/eval_results_20260408_103950.json new file mode 100644 index 0000000000000000000000000000000000000000..b65d826815b5f3053b3330960a22e48ce34c3bc1 --- /dev/null +++ b/outputs/eval_results_20260408_103950.json @@ -0,0 +1,4527 @@ +{ + "metadata": { + "timestamp": "20260408_103950", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "easy", + "medium", + "hard", + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "summary": { + "easy|heuristic": { + "grader_mean": 0.7958, + "reward_mean": 7.88, + "success_rate": 0.85, + "adaptation": 1.0, + "n": 10 + }, + "medium|heuristic": { + "grader_mean": 0.7071, + "reward_mean": 3.7154, + "success_rate": 0.805, + "adaptation": 0.78, + "n": 10 + }, + "hard|heuristic": { + "grader_mean": 0.6778, + "reward_mean": 0.009, + "success_rate": 0.8137, + "adaptation": 0.8182, + "n": 10 + }, + "hard_multi|heuristic": { + "grader_mean": 0.6094, + "reward_mean": -2.3807, + "success_rate": 0.7283, + "adaptation": 0.7356, + "n": 10 + }, + "easy|llm": { + "grader_mean": 0.7446, + "reward_mean": 0.965, + "success_rate": 0.8324, + "adaptation": 1.0, + "n": 10 + }, + "medium|llm": { + "grader_mean": 0.7207, + "reward_mean": 3.3474, + "success_rate": 0.8719, + "adaptation": 0.8703, + "n": 10 + }, + "hard|llm": { + "grader_mean": 0.6593, + "reward_mean": -7.0588, + "success_rate": 0.795, + "adaptation": 0.799, + "n": 10 + }, + "hard_multi|llm": { + "grader_mean": 0.6646, + "reward_mean": -3.9864, + "success_rate": 0.8204, + "adaptation": 0.8181, + "n": 10 + } + }, + "episodes": [ + { + "task": "easy", + "seed": 0, + "policy": "heuristic", + "total_reward": 12.2, + "grader_score": 0.7809, + "success_score": 0.95, + "budget_score": 0.04, + "adaptation_score": 1.0, + "latency_score": 0.6993, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 1, + "policy": "heuristic", + "total_reward": 10.0, + "grader_score": 0.8722, + "success_score": 0.85, + "budget_score": 0.8, + "adaptation_score": 1.0, + "latency_score": 0.7358, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95 + ] + }, + { + "task": "easy", + "seed": 2, + "policy": "heuristic", + "total_reward": 6.2, + "grader_score": 0.7371, + "success_score": 0.85, + "budget_score": 0.04, + "adaptation_score": 1.0, + "latency_score": 0.6306, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + -2.25, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 3, + "policy": "heuristic", + "total_reward": 3.8, + "grader_score": 0.8171, + "success_score": 0.75, + "budget_score": 0.76, + "adaptation_score": 1.0, + "latency_score": 0.6405, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + -2.05, + -2.05, + 0.75 + ] + }, + { + "task": "easy", + "seed": 4, + "policy": "heuristic", + "total_reward": 4.6, + "grader_score": 0.7659, + "success_score": 0.8, + "budget_score": 0.32, + "adaptation_score": 1.0, + "latency_score": 0.6397, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + -2.05, + 0.95, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 5, + "policy": "heuristic", + "total_reward": 6.4, + "grader_score": 0.7388, + "success_score": 0.85, + "budget_score": 0.08, + "adaptation_score": 1.0, + "latency_score": 0.6088, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + -2.05, + 0.75, + 0.75, + -2.25, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25 + ] + }, + { + "task": "easy", + "seed": 6, + "policy": "heuristic", + "total_reward": 9.2, + "grader_score": 0.7618, + "success_score": 0.9, + "budget_score": 0.04, + "adaptation_score": 1.0, + "latency_score": 0.6789, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 7, + "policy": "heuristic", + "total_reward": 13.0, + "grader_score": 0.8889, + "success_score": 0.9, + "budget_score": 0.8, + "adaptation_score": 1.0, + "latency_score": 0.7444, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95 + ] + }, + { + "task": "easy", + "seed": 8, + "policy": "heuristic", + "total_reward": 4.0, + "grader_score": 0.8306, + "success_score": 0.75, + "budget_score": 0.8, + "adaptation_score": 1.0, + "latency_score": 0.678, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + -2.05, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + -2.05, + -2.05 + ] + }, + { + "task": "easy", + "seed": 9, + "policy": "heuristic", + "total_reward": 9.4, + "grader_score": 0.7651, + "success_score": 0.9, + "budget_score": 0.08, + "adaptation_score": 1.0, + "latency_score": 0.6655, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "medium", + "seed": 0, + "policy": "heuristic", + "total_reward": 1.2105, + "grader_score": 0.6776, + "success_score": 0.75, + "budget_score": 0.2421, + "adaptation_score": 0.7333, + "latency_score": 0.5979, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + -2.263157894736842 + ] + }, + { + "task": "medium", + "seed": 1, + "policy": "heuristic", + "total_reward": -0.9474, + "grader_score": 0.6688, + "success_score": 0.7, + "budget_score": 0.4105, + "adaptation_score": 0.6667, + "latency_score": 0.5696, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 2, + "policy": "heuristic", + "total_reward": -4.7895, + "grader_score": 0.6797, + "success_score": 0.8, + "budget_score": 0.0, + "adaptation_score": 0.8, + "latency_score": 0.6483, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -10.0 + ] + }, + { + "task": "medium", + "seed": 3, + "policy": "heuristic", + "total_reward": 1.4211, + "grader_score": 0.6855, + "success_score": 0.75, + "budget_score": 0.2842, + "adaptation_score": 0.7333, + "latency_score": 0.6058, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 4, + "policy": "heuristic", + "total_reward": 7.8421, + "grader_score": 0.7486, + "success_score": 0.85, + "budget_score": 0.3684, + "adaptation_score": 0.8, + "latency_score": 0.6416, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 5, + "policy": "heuristic", + "total_reward": 9.1579, + "grader_score": 0.7396, + "success_score": 0.9, + "budget_score": 0.0316, + "adaptation_score": 0.9333, + "latency_score": 0.6409, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 6, + "policy": "heuristic", + "total_reward": 2.0491, + "grader_score": 0.6807, + "success_score": 0.75, + "budget_score": 0.4105, + "adaptation_score": 0.6667, + "latency_score": 0.5916, + "sla_score": 0.95, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + -2.266674397906004, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 7, + "policy": "heuristic", + "total_reward": 7.4211, + "grader_score": 0.7329, + "success_score": 0.85, + "budget_score": 0.2842, + "adaptation_score": 0.8, + "latency_score": 0.6263, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 8, + "policy": "heuristic", + "total_reward": 7.6316, + "grader_score": 0.7447, + "success_score": 0.85, + "budget_score": 0.3263, + "adaptation_score": 0.8, + "latency_score": 0.6536, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 9, + "policy": "heuristic", + "total_reward": 6.1579, + "grader_score": 0.713, + "success_score": 0.85, + "budget_score": 0.0316, + "adaptation_score": 0.8667, + "latency_score": 0.6496, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842 + ] + }, + { + "task": "hard", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4984, + "grader_score": 0.5523, + "success_score": 0.5, + "budget_score": 0.0118, + "adaptation_score": 0.75, + "latency_score": 0.5601, + "sla_score": 0.9231, + "success_rate": 0.7692, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + -2.3513378877963134, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard", + "seed": 1, + "policy": "heuristic", + "total_reward": -3.7647, + "grader_score": 0.7074, + "success_score": 0.8333, + "budget_score": 0.0, + "adaptation_score": 0.8824, + "latency_score": 0.6547, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -10.0 + ] + }, + { + "task": "hard", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.8235, + "grader_score": 0.7025, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6659, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -10.0 + ] + }, + { + "task": "hard", + "seed": 3, + "policy": "heuristic", + "total_reward": 0.5294, + "grader_score": 0.6506, + "success_score": 0.75, + "budget_score": 0.1059, + "adaptation_score": 0.7368, + "latency_score": 0.5617, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard", + "seed": 4, + "policy": "heuristic", + "total_reward": 6.0588, + "grader_score": 0.7054, + "success_score": 0.85, + "budget_score": 0.0118, + "adaptation_score": 0.8421, + "latency_score": 0.6511, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard", + "seed": 5, + "policy": "heuristic", + "total_reward": -11.4706, + "grader_score": 0.6212, + "success_score": 0.7, + "budget_score": 0.0, + "adaptation_score": 0.7778, + "latency_score": 0.5283, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 10, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0588235294117645, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -10.0 + ] + }, + { + "task": "hard", + "seed": 6, + "policy": "heuristic", + "total_reward": -2.8235, + "grader_score": 0.7106, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.7063, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -10.0 + ] + }, + { + "task": "hard", + "seed": 7, + "policy": "heuristic", + "total_reward": 3.7647, + "grader_score": 0.6945, + "success_score": 0.8, + "budget_score": 0.1529, + "adaptation_score": 0.7895, + "latency_score": 0.6185, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard", + "seed": 8, + "policy": "heuristic", + "total_reward": 6.0588, + "grader_score": 0.6988, + "success_score": 0.85, + "budget_score": 0.0118, + "adaptation_score": 0.8421, + "latency_score": 0.6181, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard", + "seed": 9, + "policy": "heuristic", + "total_reward": 9.0588, + "grader_score": 0.7348, + "success_score": 0.9, + "budget_score": 0.0118, + "adaptation_score": 0.8947, + "latency_score": 0.6703, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -1.2845, + "grader_score": 0.5987, + "success_score": 0.6, + "budget_score": 0.0545, + "adaptation_score": 0.8333, + "latency_score": 0.5194, + "sla_score": 0.9333, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2844929680102175, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -1.3182, + "grader_score": 0.6235, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.7778, + "latency_score": 0.5122, + "sla_score": 1.0, + "success_rate": 0.7368, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": 1.8636, + "grader_score": 0.6609, + "success_score": 0.75, + "budget_score": 0.0727, + "adaptation_score": 0.7778, + "latency_score": 0.5971, + "sla_score": 1.0, + "success_rate": 0.7895, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "heuristic", + "total_reward": -5.3636, + "grader_score": 0.5782, + "success_score": 0.65, + "budget_score": 0.1273, + "adaptation_score": 0.6278, + "latency_score": 0.4427, + "sla_score": 1.0, + "success_rate": 0.65, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6139, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6833, + "latency_score": 0.5247, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.5898, + "success_score": 0.5, + "budget_score": 0.0364, + "adaptation_score": 0.9444, + "latency_score": 0.4774, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "heuristic", + "total_reward": 1.6364, + "grader_score": 0.6526, + "success_score": 0.75, + "budget_score": 0.0273, + "adaptation_score": 0.7778, + "latency_score": 0.5899, + "sla_score": 1.0, + "success_rate": 0.7895, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "heuristic", + "total_reward": -2.3252, + "grader_score": 0.5994, + "success_score": 0.7, + "budget_score": 0.1545, + "adaptation_score": 0.6833, + "latency_score": 0.4728, + "sla_score": 0.9, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.308996386237959, + -2.2435165665895394, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "heuristic", + "total_reward": -4.0, + "grader_score": 0.5893, + "success_score": 0.65, + "budget_score": 0.1, + "adaptation_score": 0.6667, + "latency_score": 0.4797, + "sla_score": 1.0, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "heuristic", + "total_reward": -7.6061, + "grader_score": 0.5881, + "success_score": 0.6, + "budget_score": 0.3, + "adaptation_score": 0.5833, + "latency_score": 0.5197, + "sla_score": 0.95, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + -2.5606619562032775 + ] + }, + { + "task": "easy", + "seed": 0, + "policy": "llm", + "total_reward": 12.2, + "grader_score": 0.7809, + "success_score": 0.95, + "budget_score": 0.04, + "adaptation_score": 1.0, + "latency_score": 0.6993, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 1, + "policy": "llm", + "total_reward": 10.4, + "grader_score": 0.8018, + "success_score": 0.9, + "budget_score": 0.28, + "adaptation_score": 1.0, + "latency_score": 0.6989, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 2, + "policy": "llm", + "total_reward": -10.05, + "grader_score": 0.6755, + "success_score": 0.6875, + "budget_score": 0.0, + "adaptation_score": 1.0, + "latency_score": 0.5965, + "sla_score": 1.0, + "success_rate": 0.7333, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + -0.5, + -10.0 + ] + }, + { + "task": "easy", + "seed": 3, + "policy": "llm", + "total_reward": 6.4, + "grader_score": 0.7399, + "success_score": 0.85, + "budget_score": 0.08, + "adaptation_score": 1.0, + "latency_score": 0.6144, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "task": "easy", + "seed": 4, + "policy": "llm", + "total_reward": -9.0, + "grader_score": 0.6808, + "success_score": 0.7059, + "budget_score": 0.0, + "adaptation_score": 1.0, + "latency_score": 0.595, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + -2.05, + -2.05, + 0.75, + 0.75, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + -0.5, + -10.0 + ] + }, + { + "task": "easy", + "seed": 5, + "policy": "llm", + "total_reward": -8.35, + "grader_score": 0.6877, + "success_score": 0.7333, + "budget_score": 0.0, + "adaptation_score": 1.0, + "latency_score": 0.5885, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 15, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.95, + -2.05, + 0.75, + 0.75, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + -0.5, + -10.0 + ] + }, + { + "task": "easy", + "seed": 6, + "policy": "llm", + "total_reward": -3.05, + "grader_score": 0.7285, + "success_score": 0.8, + "budget_score": 0.0, + "adaptation_score": 1.0, + "latency_score": 0.6924, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.5, + -0.5, + -10.0 + ] + }, + { + "task": "easy", + "seed": 7, + "policy": "llm", + "total_reward": 11.8, + "grader_score": 0.8467, + "success_score": 0.9, + "budget_score": 0.56, + "adaptation_score": 1.0, + "latency_score": 0.7137, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "task": "easy", + "seed": 8, + "policy": "llm", + "total_reward": 5.4, + "grader_score": 0.7946, + "success_score": 0.8, + "budget_score": 0.48, + "adaptation_score": 1.0, + "latency_score": 0.6629, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + -2.05, + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + -2.25 + ] + }, + { + "task": "easy", + "seed": 9, + "policy": "llm", + "total_reward": -6.1, + "grader_score": 0.7091, + "success_score": 0.7647, + "budget_score": 0.0, + "adaptation_score": 1.0, + "latency_score": 0.6483, + "sla_score": 1.0, + "success_rate": 0.8125, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.95, + -2.05, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + -2.25, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + -0.5, + -10.0 + ] + }, + { + "task": "medium", + "seed": 0, + "policy": "llm", + "total_reward": 9.2632, + "grader_score": 0.7476, + "success_score": 0.9, + "budget_score": 0.0526, + "adaptation_score": 0.9333, + "latency_score": 0.6651, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157 + ] + }, + { + "task": "medium", + "seed": 1, + "policy": "llm", + "total_reward": 2.0263, + "grader_score": 0.6668, + "success_score": 0.65, + "budget_score": 0.1053, + "adaptation_score": 0.9, + "latency_score": 0.6301, + "sla_score": 1.0, + "success_rate": 0.8667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + -0.5, + 0.4736842105263157, + -0.5, + 0.4736842105263157, + -0.5, + 0.4736842105263157, + -0.5, + 0.4736842105263157, + -0.5, + 0.4736842105263157 + ] + }, + { + "task": "medium", + "seed": 2, + "policy": "llm", + "total_reward": -8.2895, + "grader_score": 0.6479, + "success_score": 0.7333, + "budget_score": 0.0, + "adaptation_score": 0.7778, + "latency_score": 0.6119, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 15, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + -0.5, + -10.0 + ] + }, + { + "task": "medium", + "seed": 3, + "policy": "llm", + "total_reward": -3.7368, + "grader_score": 0.7027, + "success_score": 0.8333, + "budget_score": 0.0, + "adaptation_score": 0.8462, + "latency_score": 0.6676, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + -10.0 + ] + }, + { + "task": "medium", + "seed": 4, + "policy": "llm", + "total_reward": 13.4211, + "grader_score": 0.804, + "success_score": 0.95, + "budget_score": 0.2842, + "adaptation_score": 0.9333, + "latency_score": 0.6986, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 5, + "policy": "llm", + "total_reward": -4.3421, + "grader_score": 0.6833, + "success_score": 0.7895, + "budget_score": 0.0, + "adaptation_score": 0.8462, + "latency_score": 0.6362, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + -0.5, + -10.0 + ] + }, + { + "task": "medium", + "seed": 6, + "policy": "llm", + "total_reward": 9.0526, + "grader_score": 0.731, + "success_score": 0.9, + "budget_score": 0.0105, + "adaptation_score": 0.8667, + "latency_score": 0.6803, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157 + ] + }, + { + "task": "medium", + "seed": 7, + "policy": "llm", + "total_reward": 10.2105, + "grader_score": 0.7615, + "success_score": 0.9, + "budget_score": 0.2421, + "adaptation_score": 0.8667, + "latency_score": 0.6595, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 8, + "policy": "llm", + "total_reward": 13.2105, + "grader_score": 0.798, + "success_score": 0.95, + "budget_score": 0.2421, + "adaptation_score": 0.9333, + "latency_score": 0.7003, + "sla_score": 1.0, + "success_rate": 0.95, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + 0.9473684210526315, + -2.0526315789473686, + 0.9473684210526315, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579 + ] + }, + { + "task": "medium", + "seed": 9, + "policy": "llm", + "total_reward": -7.3421, + "grader_score": 0.6645, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.8, + "latency_score": 0.6477, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9473684210526315, + -2.0526315789473686, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + 0.7368421052631579, + -2.263157894736842, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + 0.4736842105263157, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 0, + "policy": "llm", + "total_reward": -9.0882, + "grader_score": 0.6455, + "success_score": 0.7143, + "budget_score": 0.0, + "adaptation_score": 0.75, + "latency_score": 0.6559, + "sla_score": 1.0, + "success_rate": 0.7692, + "steps": 14, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 1, + "policy": "llm", + "total_reward": -9.9706, + "grader_score": 0.6376, + "success_score": 0.6923, + "budget_score": 0.0, + "adaptation_score": 0.8182, + "latency_score": 0.5812, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 13, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 2, + "policy": "llm", + "total_reward": -4.5294, + "grader_score": 0.6905, + "success_score": 0.8235, + "budget_score": 0.0, + "adaptation_score": 0.8125, + "latency_score": 0.6549, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + -10.0 + ] + }, + { + "task": "hard", + "seed": 3, + "policy": "llm", + "total_reward": -5.4412, + "grader_score": 0.6716, + "success_score": 0.7778, + "budget_score": 0.0, + "adaptation_score": 0.8125, + "latency_score": 0.6287, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 4, + "policy": "llm", + "total_reward": -6.3824, + "grader_score": 0.6754, + "success_score": 0.7647, + "budget_score": 0.0, + "adaptation_score": 0.8, + "latency_score": 0.6797, + "sla_score": 1.0, + "success_rate": 0.8125, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 5, + "policy": "llm", + "total_reward": -11.9706, + "grader_score": 0.6021, + "success_score": 0.6364, + "budget_score": 0.0, + "adaptation_score": 0.7778, + "latency_score": 0.5283, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 11, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0588235294117645, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 6, + "policy": "llm", + "total_reward": -7.0294, + "grader_score": 0.6671, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.7857, + "latency_score": 0.6747, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 7, + "policy": "llm", + "total_reward": -8.7059, + "grader_score": 0.6328, + "success_score": 0.6875, + "budget_score": 0.0, + "adaptation_score": 0.7692, + "latency_score": 0.6137, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 8, + "policy": "llm", + "total_reward": -8.5882, + "grader_score": 0.6287, + "success_score": 0.6875, + "budget_score": 0.0, + "adaptation_score": 0.7692, + "latency_score": 0.5929, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -2.2941176470588234, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + 0.4117647058823529, + -0.5, + 0.4117647058823529, + -0.5, + -10.0 + ] + }, + { + "task": "hard", + "seed": 9, + "policy": "llm", + "total_reward": 1.1176, + "grader_score": 0.7418, + "success_score": 0.9, + "budget_score": 0.0, + "adaptation_score": 0.8947, + "latency_score": 0.7143, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9411764705882353, + 0.9411764705882353, + -2.0588235294117645, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + 0.7058823529411764, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": -6.4091, + "grader_score": 0.6622, + "success_score": 0.7647, + "budget_score": 0.0, + "adaptation_score": 0.8056, + "latency_score": 0.6086, + "sla_score": 1.0, + "success_rate": 0.8125, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": -7.8182, + "grader_score": 0.6482, + "success_score": 0.7059, + "budget_score": 0.0, + "adaptation_score": 0.8444, + "latency_score": 0.5875, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": -4.1364, + "grader_score": 0.6839, + "success_score": 0.7895, + "budget_score": 0.0, + "adaptation_score": 0.8194, + "latency_score": 0.6659, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "llm", + "total_reward": 0.0455, + "grader_score": 0.6224, + "success_score": 0.7, + "budget_score": 0.0091, + "adaptation_score": 0.7639, + "latency_score": 0.5411, + "sla_score": 1.0, + "success_rate": 0.7778, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_b", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -2.4545454545454546, + -0.5, + -2.2272727272727275, + -0.5, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.6939, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.8819, + "latency_score": 0.6238, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 5, + "policy": "llm", + "total_reward": -9.3182, + "grader_score": 0.6234, + "success_score": 0.7143, + "budget_score": 0.0, + "adaptation_score": 0.7778, + "latency_score": 0.5176, + "sla_score": 1.0, + "success_rate": 0.7692, + "steps": 14, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 6, + "policy": "llm", + "total_reward": -3.6364, + "grader_score": 0.7025, + "success_score": 0.8333, + "budget_score": 0.0, + "adaptation_score": 0.8194, + "latency_score": 0.6931, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 7, + "policy": "llm", + "total_reward": -5.0909, + "grader_score": 0.6715, + "success_score": 0.7778, + "budget_score": 0.0, + "adaptation_score": 0.8175, + "latency_score": 0.6236, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 8, + "policy": "llm", + "total_reward": -6.4091, + "grader_score": 0.6443, + "success_score": 0.7, + "budget_score": 0.0, + "adaptation_score": 0.8175, + "latency_score": 0.6039, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 9, + "policy": "llm", + "total_reward": -3.1818, + "grader_score": 0.694, + "success_score": 0.8, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6866, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260408_105938.json b/outputs/eval_results_20260408_105938.json new file mode 100644 index 0000000000000000000000000000000000000000..2ad3c093debf5bcab35f3d9b248d1f6cef1a8cd7 --- /dev/null +++ b/outputs/eval_results_20260408_105938.json @@ -0,0 +1,595 @@ +{ + "metadata": { + "timestamp": "20260408_105938", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6285, + "reward_mean": -1.2513, + "success_rate": 0.7368, + "adaptation": 0.765, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6732, + "reward_mean": -3.9, + "success_rate": 0.8246, + "adaptation": 0.852, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -5.5909, + "grader_score": 0.5947, + "success_score": 0.6, + "budget_score": 0.0818, + "adaptation_score": 0.7014, + "latency_score": 0.5608, + "sla_score": 1.0, + "success_rate": 0.6667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 0.1553, + "grader_score": 0.637, + "success_score": 0.75, + "budget_score": 0.0364, + "adaptation_score": 0.7944, + "latency_score": 0.526, + "sla_score": 0.95, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2537758197481086, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6186, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6833, + "latency_score": 0.5485, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": 1.5881, + "grader_score": 0.6558, + "success_score": 0.75, + "budget_score": 0.0364, + "adaptation_score": 0.8333, + "latency_score": 0.5827, + "sla_score": 0.9474, + "success_rate": 0.7895, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.320955039130961, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": 0.1818, + "grader_score": 0.6362, + "success_score": 0.7, + "budget_score": 0.0364, + "adaptation_score": 0.8125, + "latency_score": 0.5412, + "sla_score": 1.0, + "success_rate": 0.7778, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -8.8182, + "grader_score": 0.648, + "success_score": 0.6875, + "budget_score": 0.0, + "adaptation_score": 0.8194, + "latency_score": 0.6393, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -8.8182, + "grader_score": 0.6318, + "success_score": 0.6875, + "budget_score": 0.0, + "adaptation_score": 0.8194, + "latency_score": 0.5585, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 7.5455, + "grader_score": 0.7177, + "success_score": 0.85, + "budget_score": 0.0091, + "adaptation_score": 0.8889, + "latency_score": 0.6677, + "sla_score": 1.0, + "success_rate": 0.8947, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -4.3182, + "grader_score": 0.6892, + "success_score": 0.7895, + "budget_score": 0.0, + "adaptation_score": 0.875, + "latency_score": 0.6367, + "sla_score": 1.0, + "success_rate": 0.8333, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -5.0909, + "grader_score": 0.6795, + "success_score": 0.7778, + "budget_score": 0.0, + "adaptation_score": 0.8571, + "latency_score": 0.6237, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 18, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_200102.json b/outputs/eval_results_20260424_200102.json new file mode 100644 index 0000000000000000000000000000000000000000..9ae63ce80014673037e4fd45a0b86fb552a37ecb --- /dev/null +++ b/outputs/eval_results_20260424_200102.json @@ -0,0 +1,613 @@ +{ + "metadata": { + "timestamp": "20260424_200102", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.543, + "reward_mean": -5.258, + "success_rate": 0.7413, + "adaptation": 0.7319, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -16.5629, + "grader_score": 0.3987, + "success_score": 0.6, + "budget_score": 0.0, + "adaptation_score": 0.5778, + "latency_score": 0.4679, + "sla_score": 0.95, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 4.5909, + "grader_score": 0.6898, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.8377, + "latency_score": 0.6478, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -10.0909, + "grader_score": 0.4621, + "success_score": 0.6842, + "budget_score": 0.0, + "adaptation_score": 0.75, + "latency_score": 0.5541, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_201654.json b/outputs/eval_results_20260424_201654.json new file mode 100644 index 0000000000000000000000000000000000000000..a5419ec5c1d1fd94e3de8c43fd5c53cade7a4fcf --- /dev/null +++ b/outputs/eval_results_20260424_201654.json @@ -0,0 +1,142 @@ +{ + "metadata": { + "timestamp": "20260424_201654", + "policies": [ + "heuristic" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6106, + "reward_mean": -1.8269, + "success_rate": 0.7158, + "adaptation": 0.6984, + "n": 2 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_201736.json b/outputs/eval_results_20260424_201736.json new file mode 100644 index 0000000000000000000000000000000000000000..9fd564c966830133b2c5dae24d81df8a5d00f9c5 --- /dev/null +++ b/outputs/eval_results_20260424_201736.json @@ -0,0 +1,611 @@ +{ + "metadata": { + "timestamp": "20260424_201736", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6216, + "reward_mean": -0.8455, + "success_rate": 0.7911, + "adaptation": 0.7914, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -9.1364, + "grader_score": 0.4588, + "success_score": 0.6471, + "budget_score": 0.0, + "adaptation_score": 0.7639, + "latency_score": 0.5739, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 17, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": 6.4091, + "grader_score": 0.7037, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.9, + "latency_score": 0.607, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 4.0, + "grader_score": 0.7094, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.9091, + "latency_score": 0.6879, + "sla_score": 1.0, + "success_rate": 0.875, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -3.8636, + "grader_score": 0.5931, + "success_score": 0.6, + "budget_score": 0.1273, + "adaptation_score": 0.7232, + "latency_score": 0.4968, + "sla_score": 1.0, + "success_rate": 0.7059, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_204919.json b/outputs/eval_results_20260424_204919.json new file mode 100644 index 0000000000000000000000000000000000000000..fa700a77f2377237205ee2a067004afa3868c577 --- /dev/null +++ b/outputs/eval_results_20260424_204919.json @@ -0,0 +1,607 @@ +{ + "metadata": { + "timestamp": "20260424_204919", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.5074, + "reward_mean": -7.1695, + "success_rate": 0.7384, + "adaptation": 0.7262, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -16.5629, + "grader_score": 0.3987, + "success_score": 0.6, + "budget_score": 0.0, + "adaptation_score": 0.5778, + "latency_score": 0.4679, + "sla_score": 0.95, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": -7.7389, + "grader_score": 0.4774, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.7216, + "latency_score": 0.6235, + "sla_score": 0.95, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.0454545454545454, + -2.2388549944511897, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -7.3182, + "grader_score": 0.4963, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.8375, + "latency_score": 0.5959, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_205300.json b/outputs/eval_results_20260424_205300.json new file mode 100644 index 0000000000000000000000000000000000000000..aa5eca8bbf6b25ac0a8cf1c7d8548e69106d5d22 --- /dev/null +++ b/outputs/eval_results_20260424_205300.json @@ -0,0 +1,607 @@ +{ + "metadata": { + "timestamp": "20260424_205300", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.5399, + "reward_mean": -5.6126, + "success_rate": 0.7384, + "adaptation": 0.7296, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -16.5629, + "grader_score": 0.3987, + "success_score": 0.6, + "budget_score": 0.0, + "adaptation_score": 0.5778, + "latency_score": 0.4679, + "sla_score": 0.95, + "success_rate": 0.6, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 0.0455, + "grader_score": 0.6399, + "success_score": 0.75, + "budget_score": 0.0091, + "adaptation_score": 0.7386, + "latency_score": 0.5792, + "sla_score": 1.0, + "success_rate": 0.75, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -7.3182, + "grader_score": 0.4963, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.8375, + "latency_score": 0.5959, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 16, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_212358.json b/outputs/eval_results_20260424_212358.json new file mode 100644 index 0000000000000000000000000000000000000000..16111086876f05fd34ac13716994669b1c1f652d --- /dev/null +++ b/outputs/eval_results_20260424_212358.json @@ -0,0 +1,617 @@ +{ + "metadata": { + "timestamp": "20260424_212358", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6055, + "reward_mean": -2.1217, + "success_rate": 0.7532, + "adaptation": 0.7462, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": 6.4091, + "grader_score": 0.7037, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.9, + "latency_score": 0.607, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 2.5, + "grader_score": 0.6803, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.8091, + "latency_score": 0.6422, + "sla_score": 1.0, + "success_rate": 0.8235, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -10.8182, + "grader_score": 0.4544, + "success_score": 0.65, + "budget_score": 0.0, + "adaptation_score": 0.75, + "latency_score": 0.5542, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_214229.json b/outputs/eval_results_20260424_214229.json new file mode 100644 index 0000000000000000000000000000000000000000..094f19ab87e3eeb933c6fb85862ba6cb149beb63 --- /dev/null +++ b/outputs/eval_results_20260424_214229.json @@ -0,0 +1,605 @@ +{ + "metadata": { + "timestamp": "20260424_214229", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6079, + "reward_mean": 0.3818, + "success_rate": 0.8525, + "adaptation": 0.8604, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -8.1364, + "grader_score": 0.4778, + "success_score": 0.7333, + "budget_score": 0.0, + "adaptation_score": 0.7639, + "latency_score": 0.5716, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 15, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": 0.1818, + "grader_score": 0.6384, + "success_score": 0.6, + "budget_score": 0.0364, + "adaptation_score": 0.9375, + "latency_score": 0.5773, + "sla_score": 1.0, + "success_rate": 0.8571, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": 6.3636, + "grader_score": 0.6953, + "success_score": 0.8, + "budget_score": 0.0727, + "adaptation_score": 0.8583, + "latency_score": 0.6138, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_215216.json b/outputs/eval_results_20260424_215216.json new file mode 100644 index 0000000000000000000000000000000000000000..80833fddfb2f6ba37a7c4352005607ba78047d3d --- /dev/null +++ b/outputs/eval_results_20260424_215216.json @@ -0,0 +1,83 @@ +{ + "metadata": { + "timestamp": "20260424_215216", + "policies": [ + "heuristic" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.5459, + "reward_mean": -7.0629, + "success_rate": 0.6316, + "adaptation": 0.6111, + "n": 1 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_215221.json b/outputs/eval_results_20260424_215221.json new file mode 100644 index 0000000000000000000000000000000000000000..cdcfff1b29d29b81c89ffdfa5c446cc09d8a1d5c --- /dev/null +++ b/outputs/eval_results_20260424_215221.json @@ -0,0 +1,83 @@ +{ + "metadata": { + "timestamp": "20260424_215221", + "policies": [ + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100 + ] + }, + "summary": { + "hard_multi|llm": { + "grader_mean": 0.15, + "reward_mean": -10.0, + "success_rate": 0.0, + "adaptation": 0.0, + "n": 1 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -10.0, + "grader_score": 0.15, + "success_score": 0.0, + "budget_score": 1.0, + "adaptation_score": 0.0, + "latency_score": 0.0, + "sla_score": 0.0, + "success_rate": 0.0, + "steps": 20, + "actions": [ + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260424_215420.json b/outputs/eval_results_20260424_215420.json new file mode 100644 index 0000000000000000000000000000000000000000..b241261e4600c1643a3e3871ac21d669d2ce523f --- /dev/null +++ b/outputs/eval_results_20260424_215420.json @@ -0,0 +1,603 @@ +{ + "metadata": { + "timestamp": "20260424_215420", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.5716, + "reward_mean": -1.4182, + "success_rate": 0.8432, + "adaptation": 0.8471, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -8.1364, + "grader_score": 0.4778, + "success_score": 0.7333, + "budget_score": 0.0, + "adaptation_score": 0.7639, + "latency_score": 0.5716, + "sla_score": 1.0, + "success_rate": 0.7857, + "steps": 15, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": 0.1818, + "grader_score": 0.6364, + "success_score": 0.6, + "budget_score": 0.0364, + "adaptation_score": 0.9375, + "latency_score": 0.567, + "sla_score": 1.0, + "success_rate": 0.8571, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -2.6364, + "grader_score": 0.5158, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.7917, + "latency_score": 0.6341, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260425_143356.json b/outputs/eval_results_20260425_143356.json new file mode 100644 index 0000000000000000000000000000000000000000..a3240e22f884b0c505a834b92885d221fe975e21 --- /dev/null +++ b/outputs/eval_results_20260425_143356.json @@ -0,0 +1,383 @@ +{ + "metadata": { + "timestamp": "20260425_143356", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.5937, + "reward_mean": -3.0795, + "success_rate": 0.6947, + "adaptation": 0.6407, + "n": 3 + }, + "hard_multi|llm": { + "grader_mean": 0.6989, + "reward_mean": 6.2879, + "success_rate": 0.8887, + "adaptation": 0.8675, + "n": 3 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4659, + "grader_score": 0.5569, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.6032, + "latency_score": 0.4686, + "sla_score": 0.9474, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.3750364951788474, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -2.7727, + "grader_score": 0.6077, + "success_score": 0.7, + "budget_score": 0.0455, + "adaptation_score": 0.6833, + "latency_score": 0.5213, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.0, + "grader_score": 0.6165, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.6357, + "latency_score": 0.4967, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": 4.7727, + "grader_score": 0.6818, + "success_score": 0.75, + "budget_score": 0.0545, + "adaptation_score": 0.8571, + "latency_score": 0.6358, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": 6.1364, + "grader_score": 0.6994, + "success_score": 0.8, + "budget_score": 0.0273, + "adaptation_score": 0.8786, + "latency_score": 0.648, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": 7.9545, + "grader_score": 0.7156, + "success_score": 0.85, + "budget_score": 0.0909, + "adaptation_score": 0.8667, + "latency_score": 0.6181, + "sla_score": 1.0, + "success_rate": 0.8947, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260425_143711.json b/outputs/eval_results_20260425_143711.json new file mode 100644 index 0000000000000000000000000000000000000000..f23b92ce510d61d3f1c080bea22af6d7ae6ed129 --- /dev/null +++ b/outputs/eval_results_20260425_143711.json @@ -0,0 +1,617 @@ +{ + "metadata": { + "timestamp": "20260425_143711", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6007, + "reward_mean": -3.0638, + "success_rate": 0.6937, + "adaptation": 0.6536, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6935, + "reward_mean": 5.6091, + "success_rate": 0.8846, + "adaptation": 0.8744, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 0, + "policy": "heuristic", + "total_reward": -4.4659, + "grader_score": 0.5569, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.6032, + "latency_score": 0.4686, + "sla_score": 0.9474, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.3750364951788474, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "heuristic", + "total_reward": -2.7727, + "grader_score": 0.6077, + "success_score": 0.7, + "budget_score": 0.0455, + "adaptation_score": 0.6833, + "latency_score": 0.5213, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "heuristic", + "total_reward": -2.0, + "grader_score": 0.6165, + "success_score": 0.7, + "budget_score": 0.2, + "adaptation_score": 0.6357, + "latency_score": 0.4967, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "heuristic", + "total_reward": -1.9895, + "grader_score": 0.6289, + "success_score": 0.7, + "budget_score": 0.2091, + "adaptation_score": 0.6833, + "latency_score": 0.5416, + "sla_score": 0.95, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.262190038025986, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "heuristic", + "total_reward": -4.0909, + "grader_score": 0.5933, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.6625, + "latency_score": 0.5175, + "sla_score": 1.0, + "success_rate": 0.6842, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 0, + "policy": "llm", + "total_reward": 4.7727, + "grader_score": 0.6818, + "success_score": 0.75, + "budget_score": 0.0545, + "adaptation_score": 0.8571, + "latency_score": 0.6358, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 1, + "policy": "llm", + "total_reward": 6.1364, + "grader_score": 0.6994, + "success_score": 0.8, + "budget_score": 0.0273, + "adaptation_score": 0.8786, + "latency_score": 0.648, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 2, + "policy": "llm", + "total_reward": 7.9545, + "grader_score": 0.7156, + "success_score": 0.85, + "budget_score": 0.0909, + "adaptation_score": 0.8667, + "latency_score": 0.6181, + "sla_score": 1.0, + "success_rate": 0.8947, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 3, + "policy": "llm", + "total_reward": 9.0455, + "grader_score": 0.7388, + "success_score": 0.9, + "budget_score": 0.0091, + "adaptation_score": 0.8944, + "latency_score": 0.6926, + "sla_score": 1.0, + "success_rate": 0.9, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 4, + "policy": "llm", + "total_reward": 0.1364, + "grader_score": 0.6318, + "success_score": 0.6, + "budget_score": 0.0273, + "adaptation_score": 0.875, + "latency_score": 0.6137, + "sla_score": 1.0, + "success_rate": 0.8571, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260425_144313.json b/outputs/eval_results_20260425_144313.json new file mode 100644 index 0000000000000000000000000000000000000000..e06470d5cb45300b236d9af19cd6e9b4bb57c01e --- /dev/null +++ b/outputs/eval_results_20260425_144313.json @@ -0,0 +1,613 @@ +{ + "metadata": { + "timestamp": "20260425_144313", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.5938, + "reward_mean": -0.3182, + "success_rate": 0.8368, + "adaptation": 0.8435, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -1.1364, + "grader_score": 0.6114, + "success_score": 0.55, + "budget_score": 0.0727, + "adaptation_score": 0.8889, + "latency_score": 0.5387, + "sla_score": 1.0, + "success_rate": 0.8462, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": -1.3182, + "grader_score": 0.6135, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.7946, + "latency_score": 0.5208, + "sla_score": 1.0, + "success_rate": 0.7647, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": -2.6364, + "grader_score": 0.5158, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.7917, + "latency_score": 0.6341, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260425_151917.json b/outputs/eval_results_20260425_151917.json new file mode 100644 index 0000000000000000000000000000000000000000..34bc823ac8d04b4936cabfbd4d60ef500ab1003c --- /dev/null +++ b/outputs/eval_results_20260425_151917.json @@ -0,0 +1,615 @@ +{ + "metadata": { + "timestamp": "20260425_151917", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6175, + "reward_mean": -2.1399, + "success_rate": 0.7108, + "adaptation": 0.7001, + "n": 5 + }, + "hard_multi|llm": { + "grader_mean": 0.6352, + "reward_mean": 1.8273, + "success_rate": 0.8557, + "adaptation": 0.8688, + "n": 5 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -1.1364, + "grader_score": 0.6114, + "success_score": 0.55, + "budget_score": 0.0727, + "adaptation_score": 0.8889, + "latency_score": 0.5387, + "sla_score": 1.0, + "success_rate": 0.8462, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": 0.4091, + "grader_score": 0.6413, + "success_score": 0.65, + "budget_score": 0.0818, + "adaptation_score": 0.8542, + "latency_score": 0.5659, + "sla_score": 1.0, + "success_rate": 0.8125, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": 6.3636, + "grader_score": 0.6953, + "success_score": 0.8, + "budget_score": 0.0727, + "adaptation_score": 0.8583, + "latency_score": 0.6138, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_results_20260425_152953.json b/outputs/eval_results_20260425_152953.json new file mode 100644 index 0000000000000000000000000000000000000000..847858b9ca7e0f463c7a6ae0f8d6f68b3c161b62 --- /dev/null +++ b/outputs/eval_results_20260425_152953.json @@ -0,0 +1,1198 @@ +{ + "metadata": { + "timestamp": "20260425_152953", + "policies": [ + "heuristic", + "llm" + ], + "tasks": [ + "hard_multi" + ], + "seeds": [ + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109 + ] + }, + "summary": { + "hard_multi|heuristic": { + "grader_mean": 0.6064, + "reward_mean": -2.8943, + "success_rate": 0.697, + "adaptation": 0.6848, + "n": 10 + }, + "hard_multi|llm": { + "grader_mean": 0.6152, + "reward_mean": 0.8273, + "success_rate": 0.8601, + "adaptation": 0.8731, + "n": 10 + } + }, + "episodes": [ + { + "task": "hard_multi", + "seed": 100, + "policy": "heuristic", + "total_reward": -7.0629, + "grader_score": 0.5459, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.6111, + "latency_score": 0.4399, + "sla_score": 0.9474, + "success_rate": 0.6316, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2447259319640143, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "heuristic", + "total_reward": 3.4091, + "grader_score": 0.6753, + "success_score": 0.8, + "budget_score": 0.0818, + "adaptation_score": 0.7857, + "latency_score": 0.5795, + "sla_score": 1.0, + "success_rate": 0.8, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "heuristic", + "total_reward": -2.5909, + "grader_score": 0.6228, + "success_score": 0.7, + "budget_score": 0.0818, + "adaptation_score": 0.6932, + "latency_score": 0.5593, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "heuristic", + "total_reward": -2.8182, + "grader_score": 0.6003, + "success_score": 0.65, + "budget_score": 0.0364, + "adaptation_score": 0.75, + "latency_score": 0.4991, + "sla_score": 1.0, + "success_rate": 0.7222, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "heuristic", + "total_reward": -1.6364, + "grader_score": 0.6432, + "success_score": 0.7, + "budget_score": 0.2727, + "adaptation_score": 0.6607, + "latency_score": 0.5509, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275 + ] + }, + { + "task": "hard_multi", + "seed": 105, + "policy": "heuristic", + "total_reward": -5.9052, + "grader_score": 0.5453, + "success_score": 0.65, + "budget_score": 0.0455, + "adaptation_score": 0.6136, + "latency_score": 0.4287, + "sla_score": 0.9, + "success_rate": 0.65, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.3047893396608465, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2822599284433984, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 106, + "policy": "heuristic", + "total_reward": -1.8182, + "grader_score": 0.6371, + "success_score": 0.7, + "budget_score": 0.2364, + "adaptation_score": 0.6625, + "latency_score": 0.5459, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 107, + "policy": "heuristic", + "total_reward": -2.2819, + "grader_score": 0.6182, + "success_score": 0.7, + "budget_score": 0.1545, + "adaptation_score": 0.6833, + "latency_score": 0.5294, + "sla_score": 0.95, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2818707053619485, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 108, + "policy": "heuristic", + "total_reward": -5.5569, + "grader_score": 0.577, + "success_score": 0.6, + "budget_score": 0.0909, + "adaptation_score": 0.7273, + "latency_score": 0.4813, + "sla_score": 0.9444, + "success_rate": 0.6667, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.7727272727272727, + -2.2272727272727275, + -2.2386855855721066, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 109, + "policy": "heuristic", + "total_reward": -2.6818, + "grader_score": 0.5992, + "success_score": 0.7, + "budget_score": 0.0636, + "adaptation_score": 0.6607, + "latency_score": 0.4875, + "sla_score": 1.0, + "success_rate": 0.7, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.9545454545454546, + -2.0454545454545454, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + -2.2272727272727275, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454 + ] + }, + { + "task": "hard_multi", + "seed": 100, + "policy": "llm", + "total_reward": -1.1364, + "grader_score": 0.6114, + "success_score": 0.55, + "budget_score": 0.0727, + "adaptation_score": 0.8889, + "latency_score": 0.5387, + "sla_score": 1.0, + "success_rate": 0.8462, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 101, + "policy": "llm", + "total_reward": -2.5909, + "grader_score": 0.5212, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.8333, + "latency_score": 0.6282, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 102, + "policy": "llm", + "total_reward": 6.0909, + "grader_score": 0.707, + "success_score": 0.8, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.6621, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 103, + "policy": "llm", + "total_reward": 0.1818, + "grader_score": 0.6364, + "success_score": 0.6, + "budget_score": 0.0364, + "adaptation_score": 0.9375, + "latency_score": 0.567, + "sla_score": 1.0, + "success_rate": 0.8571, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 104, + "policy": "llm", + "total_reward": 6.3636, + "grader_score": 0.6953, + "success_score": 0.8, + "budget_score": 0.0727, + "adaptation_score": 0.8583, + "latency_score": 0.6138, + "sla_score": 1.0, + "success_rate": 0.8889, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 105, + "policy": "llm", + "total_reward": -0.9545, + "grader_score": 0.6218, + "success_score": 0.55, + "budget_score": 0.1091, + "adaptation_score": 0.875, + "latency_score": 0.5772, + "sla_score": 1.0, + "success_rate": 0.8462, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 106, + "policy": "llm", + "total_reward": -1.5455, + "grader_score": 0.5285, + "success_score": 0.85, + "budget_score": 0.0, + "adaptation_score": 0.8375, + "latency_score": 0.6611, + "sla_score": 1.0, + "success_rate": 0.85, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 107, + "policy": "llm", + "total_reward": -2.7273, + "grader_score": 0.5276, + "success_score": 0.8421, + "budget_score": 0.0, + "adaptation_score": 0.825, + "latency_score": 0.6789, + "sla_score": 1.0, + "success_rate": 0.8421, + "steps": 19, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -10.0 + ] + }, + { + "task": "hard_multi", + "seed": 108, + "policy": "llm", + "total_reward": 0.0909, + "grader_score": 0.631, + "success_score": 0.6, + "budget_score": 0.0182, + "adaptation_score": 0.9091, + "latency_score": 0.5821, + "sla_score": 1.0, + "success_rate": 0.8571, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5, + -0.5 + ] + }, + { + "task": "hard_multi", + "seed": 109, + "policy": "llm", + "total_reward": 4.5, + "grader_score": 0.6715, + "success_score": 0.75, + "budget_score": 0.0, + "adaptation_score": 0.8571, + "latency_score": 0.6254, + "sla_score": 1.0, + "success_rate": 0.8824, + "steps": 20, + "actions": [ + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_a", + "route_to_b", + "route_to_b", + "route_to_b", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "route_to_c", + "shed_load", + "shed_load", + "shed_load" + ], + "rewards": [ + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + 0.9545454545454546, + -2.0454545454545454, + 0.7727272727272727, + 0.7727272727272727, + -2.2272727272727275, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + 0.5454545454545454, + -0.5, + -0.5, + -0.5 + ] + } + ] +} \ No newline at end of file diff --git a/outputs/eval_summary_20260408_103950.md b/outputs/eval_summary_20260408_103950.md new file mode 100644 index 0000000000000000000000000000000000000000..d2007da781a6932a00e7e68dc5c1d3dd5be8e459 --- /dev/null +++ b/outputs/eval_summary_20260408_103950.md @@ -0,0 +1,8 @@ +# Budget Router Evaluation — 20260408_103950 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Easy | 0.7958 (n=10) | 0.7446 (n=10) | | +| Medium | 0.7071 (n=10) | 0.7207 (n=10) | | +| Hard | 0.6778 (n=10) | 0.6593 (n=10) | | +| Hard_Multi | 0.6094 (n=10) | 0.6646 (n=10) | LLM +5.5% vs heuristic | diff --git a/outputs/eval_summary_20260408_105938.md b/outputs/eval_summary_20260408_105938.md new file mode 100644 index 0000000000000000000000000000000000000000..599c03808f210e1e165f22b1cb21e60a090223ce --- /dev/null +++ b/outputs/eval_summary_20260408_105938.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260408_105938 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6285 (n=5) | 0.6732 (n=5) | LLM +4.5% vs heuristic | diff --git a/outputs/eval_summary_20260424_200102.md b/outputs/eval_summary_20260424_200102.md new file mode 100644 index 0000000000000000000000000000000000000000..671183df38310f28c44aa30df1724bd97efe734f --- /dev/null +++ b/outputs/eval_summary_20260424_200102.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_200102 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.5430 (n=5) | | diff --git a/outputs/eval_summary_20260424_201654.md b/outputs/eval_summary_20260424_201654.md new file mode 100644 index 0000000000000000000000000000000000000000..1683135297ccc8d0fea9632471a76dee06a5e86d --- /dev/null +++ b/outputs/eval_summary_20260424_201654.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_201654 + +| Task | HEURISTIC Grader | Notes | +|---|---|---| +| Hard_Multi | 0.6106 (n=2) | | diff --git a/outputs/eval_summary_20260424_201736.md b/outputs/eval_summary_20260424_201736.md new file mode 100644 index 0000000000000000000000000000000000000000..d01e5c26c1dfeb2c1d68c90be9b61bd048ff2c5c --- /dev/null +++ b/outputs/eval_summary_20260424_201736.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_201736 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6216 (n=5) | LLM +0.4 points vs heuristic | diff --git a/outputs/eval_summary_20260424_204919.md b/outputs/eval_summary_20260424_204919.md new file mode 100644 index 0000000000000000000000000000000000000000..d2e5611a747b9f92c78ff0aac71d3a62fc37c681 --- /dev/null +++ b/outputs/eval_summary_20260424_204919.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_204919 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.5074 (n=5) | | diff --git a/outputs/eval_summary_20260424_205300.md b/outputs/eval_summary_20260424_205300.md new file mode 100644 index 0000000000000000000000000000000000000000..4cfa49d0c03c94a35b638657aa7681e7e59657ae --- /dev/null +++ b/outputs/eval_summary_20260424_205300.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_205300 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.5399 (n=5) | | diff --git a/outputs/eval_summary_20260424_212358.md b/outputs/eval_summary_20260424_212358.md new file mode 100644 index 0000000000000000000000000000000000000000..4f109c75f1815ac5e48cb8ab9ec974d47465c40d --- /dev/null +++ b/outputs/eval_summary_20260424_212358.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_212358 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6055 (n=5) | | diff --git a/outputs/eval_summary_20260424_214229.md b/outputs/eval_summary_20260424_214229.md new file mode 100644 index 0000000000000000000000000000000000000000..c11288c505357c3ee8c6caad4781175a5f83b5ac --- /dev/null +++ b/outputs/eval_summary_20260424_214229.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_214229 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6079 (n=5) | | diff --git a/outputs/eval_summary_20260424_215216.md b/outputs/eval_summary_20260424_215216.md new file mode 100644 index 0000000000000000000000000000000000000000..5d6f7ef93548b4b7afd84126011218c11554c14f --- /dev/null +++ b/outputs/eval_summary_20260424_215216.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_215216 + +| Task | HEURISTIC Grader | Notes | +|---|---|---| +| Hard_Multi | 0.5459 (n=1) | | diff --git a/outputs/eval_summary_20260424_215221.md b/outputs/eval_summary_20260424_215221.md new file mode 100644 index 0000000000000000000000000000000000000000..4ba662aedda21c4c13dfe03d42d5d29b31a89e53 --- /dev/null +++ b/outputs/eval_summary_20260424_215221.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_215221 + +| Task | LLM Grader | Notes | +|---|---|---| +| Hard_Multi | 0.1500 (n=1) | | diff --git a/outputs/eval_summary_20260424_215420.md b/outputs/eval_summary_20260424_215420.md new file mode 100644 index 0000000000000000000000000000000000000000..a0cb2f1c54e308762dfbe7e4c18a06b45579cfbf --- /dev/null +++ b/outputs/eval_summary_20260424_215420.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260424_215420 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.5716 (n=5) | | diff --git a/outputs/eval_summary_20260425_143356.md b/outputs/eval_summary_20260425_143356.md new file mode 100644 index 0000000000000000000000000000000000000000..7fad3b5a0f1cac175b518fdd1ac3b50feed88a24 --- /dev/null +++ b/outputs/eval_summary_20260425_143356.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_143356 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.5937 (n=3) | 0.6989 (n=3) | LLM +10.5 points vs heuristic | diff --git a/outputs/eval_summary_20260425_143711.md b/outputs/eval_summary_20260425_143711.md new file mode 100644 index 0000000000000000000000000000000000000000..1a892a66266f896b1fbc88af6bd96f82de71d328 --- /dev/null +++ b/outputs/eval_summary_20260425_143711.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_143711 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6007 (n=5) | 0.6935 (n=5) | LLM +9.3 points vs heuristic | diff --git a/outputs/eval_summary_20260425_144313.md b/outputs/eval_summary_20260425_144313.md new file mode 100644 index 0000000000000000000000000000000000000000..608e79c3c3ba96c6fa2616bf9534ffd0891cc71f --- /dev/null +++ b/outputs/eval_summary_20260425_144313.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_144313 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.5938 (n=5) | | diff --git a/outputs/eval_summary_20260425_151917.md b/outputs/eval_summary_20260425_151917.md new file mode 100644 index 0000000000000000000000000000000000000000..0a834410e61d2ae268bf6bcef259e645086c2d7e --- /dev/null +++ b/outputs/eval_summary_20260425_151917.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_151917 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6175 (n=5) | 0.6352 (n=5) | LLM +1.8 points vs heuristic | diff --git a/outputs/eval_summary_20260425_152953.md b/outputs/eval_summary_20260425_152953.md new file mode 100644 index 0000000000000000000000000000000000000000..d7562634d4c0070aa1839a0ddc7dc3ce2273bf68 --- /dev/null +++ b/outputs/eval_summary_20260425_152953.md @@ -0,0 +1,5 @@ +# Budget Router Evaluation — 20260425_152953 + +| Task | HEURISTIC Grader | LLM Grader | Notes | +|---|---|---|---| +| Hard_Multi | 0.6064 (n=10) | 0.6152 (n=10) | LLM +0.9 points vs heuristic | diff --git a/outputs/grpo_eval_qwen3_1.7b_n10.log b/outputs/grpo_eval_qwen3_1.7b_n10.log new file mode 100644 index 0000000000000000000000000000000000000000..70475db23f54c45bf9baeacf9b51d707279f25b6 --- /dev/null +++ b/outputs/grpo_eval_qwen3_1.7b_n10.log @@ -0,0 +1,3 @@ +usage: eval_trained.py [-h] [--model-path MODEL_PATH] + [--n-episodes N_EPISODES] +eval_trained.py: error: unrecognized arguments: --max-completion-length 3500 diff --git a/outputs/grpo_probe_0.6b_n8_t12_explore.log b/outputs/grpo_probe_0.6b_n8_t12_explore.log new file mode 100644 index 0000000000000000000000000000000000000000..222057c87dc5af962b59fc8a8db54469db1425cb --- /dev/null +++ b/outputs/grpo_probe_0.6b_n8_t12_explore.log @@ -0,0 +1,114 @@ +==================================================================== +GRPO Learning Experiment — Budget Router +==================================================================== +Device : MPS +Model : Qwen/Qwen3-0.6B +Steps : 30 +Prompt : explore +Sampling : temperature=1.2 top_p=0.95 +Torch : 2.11.0 +==================================================================== + +Loading model... + Loading weights: 0%| | 0/311 [00:00 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.413\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 206.26", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.510\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 254.81", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.270\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 135.06", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.358\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 178.81", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.228\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 114.21", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.200\nsystem_latency: 0.792\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 395.81", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.400\nsystem_latency: 0.913\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:-0.200 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 456.30", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.300\nsystem_latency: 0.603\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 301.30", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.200\nsystem_latency: 0.356\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 178.05", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.100\nsystem_latency: 0.264\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 131.91", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.000\nsystem_latency: 0.250\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 125.22", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.000\nsystem_latency: 0.349\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 174.48", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.385\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 192.45", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.191\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 95.46", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.572\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 286.19", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1081, "teacher": "ppo", "teacher_score": 0.6559} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.19690000000000002, "heuristic_score": 0.4932, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.315\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 157.63", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.161\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 80.39", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.347\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 173.26", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.287\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 143.63", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.282\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 140.83", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.327\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 163.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.378\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 189.01", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.441\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 220.29", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.148\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 74.07", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.396\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 197.97", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.709\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 354.34", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.485\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 242.74", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.522\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 261.14", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.447\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 223.67", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.459\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 229.32", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1060, "teacher": "ppo", "teacher_score": 0.6901} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1634, "heuristic_score": 0.54, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.164\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 82.17", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.346\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 172.84", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.491\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 245.45", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.321\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 160.32", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.199\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 99.54", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.329\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 164.65", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.067\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 33.34", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.220\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 109.85", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.294\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.271\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 135.71", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.868\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 434.06", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.407\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 203.39", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.459\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 229.30", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.280\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 140.22", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.442\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 220.79", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1087, "teacher": "ppo", "teacher_score": 0.7034} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.14520000000000005, "heuristic_score": 0.4432, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.530\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 265.12", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.395\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 197.67", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.326\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 162.93", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.750\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.200\nsystem_latency: 0.804\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:-0.125 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 401.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.100\nsystem_latency: 0.292\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.300\nsystem_latency: 0.755\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:-0.075 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 377.26", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.200\nsystem_latency: 0.339\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 169.57", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.400\nsystem_latency: 0.934\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 466.90", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.545\nqueue_backlog: 0.300\nsystem_latency: 0.471\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~6 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 235.38", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.200\nsystem_latency: 0.531\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 265.60", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.100\nsystem_latency: 0.612\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 306.20", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.379\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 189.28", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.489\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 244.29", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.337\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 168.54", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.400\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1048, "teacher": "ppo", "teacher_score": 0.5884} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1381, "heuristic_score": 0.5062, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.327\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 163.30", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.277\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 138.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.294\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.92", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.276\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 137.87", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.139\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 69.74", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.237\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 118.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.200\nsystem_latency: 0.776\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 387.96", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.100\nsystem_latency: 0.252\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 126.01", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.300\nsystem_latency: 0.779\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 389.58", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.395\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~5 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 197.47", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.600\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 299.95", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.210\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 104.87", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.310\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 154.88", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.390\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 195.04", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.600\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1014, "teacher": "ppo", "teacher_score": 0.6443} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.13569999999999993, "heuristic_score": 0.5956, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.223\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 111.64", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.257\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 128.26", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.030\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 15.03", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.417\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 208.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.443\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 221.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.354\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 176.99", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.235\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 117.41", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.266\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 132.86", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.336\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 168.17", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.267\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 133.59", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.399\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 199.38", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.435\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 217.28", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.368\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 184.08", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.358\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 178.90", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.426\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 213.07", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1094, "teacher": "ppo", "teacher_score": 0.7313} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12770000000000004, "heuristic_score": 0.5253, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.329\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 164.27", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.441\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 220.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.667\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.200\nsystem_latency: 0.969\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:-0.167 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 484.66", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.750\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.100\nsystem_latency: 0.154\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:-0.125 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 77.05", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.405\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.067 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 202.73", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.272\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.025 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 136.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.316\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 158.19", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.505\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 252.70", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.338\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 168.82", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.428\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 214.09", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.774\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 386.94", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.270\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 135.17", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.372\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 185.98", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.317\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 158.31", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.399\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 199.70", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1076, "teacher": "ppo", "teacher_score": 0.653} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12750000000000006, "heuristic_score": 0.5627, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.037\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 18.39", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.220\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 110.22", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.352\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.82", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.349\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 174.28", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.347\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 173.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.155\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 77.59", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.238\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 118.75", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.374\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 187.04", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.200\nsystem_latency: 0.729\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 364.38", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.100\nsystem_latency: 0.332\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 166.14", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.000\nsystem_latency: 0.483\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 241.64", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 195.51", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.178\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 89.18", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.521\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 260.64", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.484\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 242.01", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1033, "teacher": "ppo", "teacher_score": 0.6902} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12650000000000006, "heuristic_score": 0.5949, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.398\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 199.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.303\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 151.71", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.207\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 103.49", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.439\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 219.66", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.260\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 129.82", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.443\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 221.36", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.503\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 251.31", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.187\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 93.62", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.318\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 158.93", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.257\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 128.25", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.407\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 203.74", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.545\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 272.44", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.398\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 198.86", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.303\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 151.73", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.587\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 293.28", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1023, "teacher": "ppo", "teacher_score": 0.7214} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12640000000000007, "heuristic_score": 0.599, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.359\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 179.41", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.186\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 92.96", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.305\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 152.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.317\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 158.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.271\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 135.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.384\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 192.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.372\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 186.14", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.148\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 73.91", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.367\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 183.38", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.440\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 220.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.457\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 228.36", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.254\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 126.86", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.524\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 262.20", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.432\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 215.98", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.438\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 218.91", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1016, "teacher": "ppo", "teacher_score": 0.7254} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12370000000000003, "heuristic_score": 0.5771, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.271\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 135.25", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.236\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 118.12", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.246\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 123.02", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.166\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 83.14", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.345\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 172.51", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.219\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 109.35", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.200\nsystem_latency: 0.773\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 386.64", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.100\nsystem_latency: 0.209\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 104.65", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.457\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 228.57", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.244\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 122.21", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 195.60", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.325\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 162.57", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.368\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 184.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.232\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 116.10", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.418\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 209.16", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1040, "teacher": "ppo", "teacher_score": 0.7008} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12229999999999996, "heuristic_score": 0.611, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.379\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 189.42", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.357\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 178.56", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.350\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.02", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.230\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 114.98", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 195.32", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.279\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 139.71", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.270\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 135.23", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.357\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 178.64", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.174\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 86.75", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.294\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.82", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.372\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 186.12", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.280\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 139.95", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.363\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 181.63", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.228\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 114.06", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.339\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 169.49", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1047, "teacher": "ppo", "teacher_score": 0.7333} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.12180000000000002, "heuristic_score": 0.5436, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.354\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 176.97", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.495\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 247.40", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.355\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 177.30", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.433\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 216.37", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.142\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 70.94", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.346\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 173.16", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.215\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 107.37", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.253\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 126.39", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.200\nsystem_latency: 0.905\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 452.45", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.500\nqueue_backlog: 0.100\nsystem_latency: 0.335\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~5 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 167.55", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.651\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 325.44", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.493\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 246.61", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.358\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 178.86", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.502\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 251.10", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1012, "teacher": "ppo", "teacher_score": 0.6654} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11929999999999996, "heuristic_score": 0.5764, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.334\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 166.78", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.314\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 157.06", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.197\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 98.45", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.474\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 237.10", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.240\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 120.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.396\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 197.77", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.268\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 133.83", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.201\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 100.42", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.258\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 128.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.256\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 127.91", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.745\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 372.44", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.576\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 288.20", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.459\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 229.36", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.451\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 225.38", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.442\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 220.80", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1002, "teacher": "ppo", "teacher_score": 0.6957} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1190000000000001, "heuristic_score": 0.5428, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.097\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 48.68", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.236\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 118.12", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.341\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 170.70", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.750\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.200\nsystem_latency: 0.873\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:-0.125 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 436.31", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.100\nsystem_latency: 0.203\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 101.25", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.217\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.025 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 108.59", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.316\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 157.91", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.174\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 87.10", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.288\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 144.00", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.200\nsystem_latency: 0.760\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 379.81", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.100\nsystem_latency: 0.404\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 201.79", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.000\nsystem_latency: 0.423\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 211.61", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.487\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 243.62", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.472\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 236.18", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.472\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 236.14", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1049, "teacher": "ppo", "teacher_score": 0.6618} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11870000000000003, "heuristic_score": 0.5757, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.328\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 164.10", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.305\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 152.51", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.244\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 122.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.310\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 155.06", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.349\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 174.59", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.414\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 207.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.351\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.221\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 110.38", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.324\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 161.98", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.282\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 141.24", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.738\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 368.80", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.476\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 238.18", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.226\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 113.17", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.567\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 283.33", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.380\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 189.98", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1097, "teacher": "ppo", "teacher_score": 0.6944} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11760000000000004, "heuristic_score": 0.6109, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.196\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 97.92", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.386\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 193.23", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.342\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 171.10", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.344\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 172.04", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.207\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 103.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.361\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 180.49", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.284\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 142.20", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.120\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 59.85", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.273\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 136.45", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.514\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 256.78", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.274\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 137.15", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.411\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 205.52", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.358\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 178.89", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.447\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 223.56", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.506\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 253.21", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1072, "teacher": "ppo", "teacher_score": 0.7285} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11649999999999994, "heuristic_score": 0.5744, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.245\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 122.34", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.443\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 221.74", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.194\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 96.75", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.360\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 180.22", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.141\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 70.44", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.187\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 93.54", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.200\nsystem_latency: 0.878\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 438.81", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.100\nsystem_latency: 0.307\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 153.58", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.337\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 168.67", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.415\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 207.69", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.388\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 194.12", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.444\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 221.91", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.452\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 226.05", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.410\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 205.11", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.442\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 220.90", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1095, "teacher": "ppo", "teacher_score": 0.6909} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11619999999999997, "heuristic_score": 0.5777, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.414\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 207.15", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.064\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 32.13", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.667\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.200\nsystem_latency: 0.711\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:-0.167 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 355.36", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.750\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.100\nsystem_latency: 0.504\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:-0.125 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 252.15", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.606\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.067 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 302.83", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.025 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 195.51", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.198\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 99.03", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.313\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 156.68", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.290\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 145.23", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.206\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 103.01", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.171\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 85.50", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.616\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 307.85", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.275\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 137.62", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.542\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 271.25", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.353\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 176.42", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1068, "teacher": "ppo", "teacher_score": 0.6939} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11480000000000001, "heuristic_score": 0.6129, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.196\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 98.15", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.379\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 189.33", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.188\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 94.19", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.302\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 150.97", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 100.16", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.377\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 188.61", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.453\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 226.44", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.317\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 158.34", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.215\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 107.73", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.347\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 173.75", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.356\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 177.87", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.458\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 229.02", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.524\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 262.20", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.452\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 226.04", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.314\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 157.23", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1082, "teacher": "ppo", "teacher_score": 0.7277} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11470000000000002, "heuristic_score": 0.6137, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.311\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 155.72", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.258\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 129.13", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.382\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 190.87", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.403\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 201.73", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.315\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 157.36", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.335\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 167.35", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.092\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 46.02", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.276\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 137.81", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.483\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 241.33", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.292\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 145.78", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.224\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 112.25", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.514\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 257.20", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.528\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 264.20", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.330\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 165.20", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.283\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 141.71", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1092, "teacher": "ppo", "teacher_score": 0.7284} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1139, "heuristic_score": 0.578, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.352\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.76", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.329\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 164.64", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.359\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 179.58", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.403\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 201.58", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.194\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 97.11", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.129\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 64.36", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.242\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 121.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.489\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 244.67", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.289\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 144.50", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.269\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 134.43", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.200\nsystem_latency: 0.708\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 353.82", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.100\nsystem_latency: 0.520\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 260.24", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.475\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 237.69", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.561\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 280.28", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.383\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 191.46", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1038, "teacher": "ppo", "teacher_score": 0.6919} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1129, "heuristic_score": 0.5684, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.324\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 162.00", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.410\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 205.12", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.265\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 132.34", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.363\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 181.64", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.346\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 172.99", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.238\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 119.14", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.350\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.02", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.353\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 176.40", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.200\nsystem_latency: 0.823\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 411.39", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.100\nsystem_latency: 0.377\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 188.43", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.000\nsystem_latency: 0.451\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 225.39", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.000\nsystem_latency: 0.346\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 173.03", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.452\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 225.88", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 195.70", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.499\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 249.64", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1083, "teacher": "ppo", "teacher_score": 0.6813} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.11030000000000006, "heuristic_score": 0.5366, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.186\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 93.19", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.202\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 100.94", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.267\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 133.49", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.750\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.200\nsystem_latency: 0.820\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:-0.125 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 410.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.100\nsystem_latency: 0.293\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:-0.100 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.35", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.303\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.025 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 151.36", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.340\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 169.99", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.372\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 185.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.200\nsystem_latency: 0.713\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: -2.23\n previous_success: false\n previous_cost: 0.05\n previous_latency_ms: 356.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.100\nsystem_latency: 0.421\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 210.53", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.455\nqueue_backlog: 0.000\nsystem_latency: 0.608\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 303.95", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.364\nqueue_backlog: 0.000\nsystem_latency: 0.346\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 172.86", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.273\nqueue_backlog: 0.000\nsystem_latency: 0.297\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 148.56", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.388\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 194.15", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.182\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.385\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~0 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 192.32", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.800\nprovider_c_status: 1.000\nbudget_remaining: 0.091\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1077, "teacher": "ppo", "teacher_score": 0.6469} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.10999999999999999, "heuristic_score": 0.6232, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.453\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 226.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.201\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 100.45", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.137\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 68.69", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.328\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 164.17", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.380\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 189.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.515\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 257.52", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.185\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 92.25", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.205\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 102.74", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.391\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 195.48", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.322\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 161.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.435\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 217.53", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.316\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 157.95", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.220\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 110.11", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.337\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 168.50", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.240\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 120.19", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1026, "teacher": "ppo", "teacher_score": 0.7332} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1049, "heuristic_score": 0.6165, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.170\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 84.82", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.366\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 182.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.403\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 201.34", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.368\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 183.97", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.475\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 237.49", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.229\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 114.44", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.411\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 205.37", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.492\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 245.83", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.226\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 112.78", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.345\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 172.31", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.130\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 65.16", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.605\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 302.74", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.469\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 234.51", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.454\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 227.02", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.411\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 205.32", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1037, "teacher": "ppo", "teacher_score": 0.7214} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.10470000000000002, "heuristic_score": 0.6267, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.375\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 187.59", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.408\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 203.87", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.082\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 41.01", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.242\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 121.18", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.387\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 193.73", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.227\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 113.28", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.351\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 175.31", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.276\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 138.09", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.299\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 149.40", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.341\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 170.60", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.264\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 132.08", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.245\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 122.40", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.456\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 227.91", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.406\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 202.98", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.448\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 224.16", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1051, "teacher": "ppo", "teacher_score": 0.7314} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.1038, "heuristic_score": 0.6283, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.293\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 146.72", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.246\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 122.77", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.175\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 87.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.195\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 97.74", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.144\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 71.90", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.317\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 158.53", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.255\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 127.46", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.300\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 149.83", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.505\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 252.65", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.374\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 187.23", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.288\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 143.94", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.434\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 217.04", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.492\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 245.99", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.418\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 208.81", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.312\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 155.92", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1054, "teacher": "ppo", "teacher_score": 0.7321} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.10339999999999994, "heuristic_score": 0.6276, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.309\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 154.51", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.247\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 123.39", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.296\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 148.11", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.263\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 131.72", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.244\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 121.79", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.280\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 140.07", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.171\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 85.28", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.278\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 139.15", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.264\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 132.14", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.343\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 171.58", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.319\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 159.68", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.501\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 250.57", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.267\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 133.48", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.512\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 255.94", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.227\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.541\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 270.50", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1070, "teacher": "ppo", "teacher_score": 0.731} +{"actions": ["route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_b", "route_to_c", "route_to_c", "shed_load", "route_to_c", "shed_load", "route_to_c", "shed_load", "shed_load", "shed_load"], "delta_vs_heuristic": 0.10199999999999998, "heuristic_score": 0.5852, "messages": [{"content": "\nYou are a cost-aware LLM API routing agent managing a production system.\nAt each step, output EXACTLY ONE action string. Nothing else.\n\nENVIRONMENT:\n Three providers: A ($0.01/req, cheapest), B ($0.05/req), C ($0.10/req, most reliable).\n provider_X_status = windowed success rate [0=always fails, 1=always succeeds].\n IMPORTANT: A status of exactly 0.500 means this provider has NEVER been routed to\n in this episode \u2014 it is unobserved, not confirmed healthy. Route to it once to get\n a real reading. Do not treat 0.500 as a health signal.\n budget_remaining: fraction of budget left. Reaching 0 = catastrophic -10 penalty.\n step_count [0\u21921], steps_remaining: episode progress (20 steps total).\n\nVALID ACTIONS (output ONLY one):\n route_to_a | route_to_b | route_to_c | shed_load\n\nGOLDEN RULE \u2014 DEFAULT STRATEGY:\n Stay on the CHEAPEST provider whose status > 0.52. Only deviate if there is CLEAR, SUSTAINED evidence of degradation (defined below). Unnecessary switching to expensive providers burns budget and reduces your score.\n\nNOISE CALIBRATION (critical):\n- Status fluctuates due to Bernoulli sampling noise. Single-step dips are not reliable signals.\n- Use the provided 2-step trend (avg/step): a sustained negative trend across multiple steps\nindicates real degradation; a trend near 0 means the provider is stable. Do NOT switch on noise.\n- REAL degradation signal: sustained negative trend AND current status is visibly declining.\n- Only when both conditions hold across consecutive observations should you consider early switching.\n- On stable tasks, trends hover near zero. Switching on noise burns budget without benefit.\n\n\nWHEN TO SWITCH (use your conversation history):\nA \u2192 B: When trend_a is clearly and consistently negative AND status_a is approaching unreliable,\n OR status_a is already below 0.52 (failure probability exceeds success probability).\nB \u2192 C: Same principle \u2014 sustained decline signals, not single-step noise.\nNever switch based on a single bad observation \u2014 noise causes occasional dips.\n\nBUDGET RUNWAY \u2014 HARD CONSTRAINT:\nbudget_runway_at_current_rate shows how many more steps you can afford at current spend rate.\nIf budget_runway_at_current_rate < steps_remaining: switch to a cheaper provider IMMEDIATELY.\nIf budget_remaining < 0.15 (less than 15% left): treat C as OFF-LIMITS unless A and B are\n both below 0.30 status. Prefer shed_load over routing C when budget is this low.\nNEVER route to any provider if doing so would leave budget_remaining below the cost of\nthat provider times the steps_remaining. The -10 bankruptcy penalty destroys all episode\nvalue accumulated so far \u2014 budget survival is non-negotiable.\nTASK PROFILES (the task name appears in each observation \u2014 use it):\n easy: Stable environment. Trend fluctuations are mostly noise. Stay on the cheapest provider unless its trend is catastrophically and sustainedly negative.\n medium: Dynamic environment. A provider may degrade mid-episode. Monitor trends and switch to the next cheapest healthy fallback if the primary fails.\n hard / hard_multi: Hostile, multi-failure environments. Multiple providers may degrade at unexpected times in unpredictable sequences.\n Your Runbook: Always map traffic to the lowest-cost healthy provider (A=$0.01, B=$0.05, C=$0.10).\n Watch your conversation history: if your currently active provider shows a clear, sustained negative trend, switch early to the next cheapest option that is healthy.\n CRITICAL: Before switching to expensive fallbacks (like C), use budget_runway to verify you can afford them to prevent budget exhaustion.\n\nOutput only the action string.", "role": "system"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 0.500\nprovider_c_status: 0.500\nbudget_remaining: 1.000\nqueue_backlog: 0.000\nsystem_latency: 0.200\nstep_count: 0.000\nsteps_remaining: 20\ntrend: unavailable\nbudget_runway_at_current_rate: >20 steps", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.955\nqueue_backlog: 0.000\nsystem_latency: 0.172\nstep_count: 0.050\nsteps_remaining: 19\ntrend (1-step only, noisy): A:+0.000 B:+0.500 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 86.05", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.909\nqueue_backlog: 0.000\nsystem_latency: 0.278\nstep_count: 0.100\nsteps_remaining: 18\ntrend (avg/step, 2-step): A:+0.000 B:+0.250 C:+0.000\nbudget_runway_at_current_rate: ~20 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 138.97", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.864\nqueue_backlog: 0.000\nsystem_latency: 0.176\nstep_count: 0.150\nsteps_remaining: 17\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~18 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 88.06", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.818\nqueue_backlog: 0.000\nsystem_latency: 0.193\nstep_count: 0.200\nsteps_remaining: 16\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 96.52", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.773\nqueue_backlog: 0.000\nsystem_latency: 0.430\nstep_count: 0.250\nsteps_remaining: 15\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~17 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 215.12", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.727\nqueue_backlog: 0.000\nsystem_latency: 0.140\nstep_count: 0.300\nsteps_remaining: 14\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~15 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 70.08", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.682\nqueue_backlog: 0.000\nsystem_latency: 0.279\nstep_count: 0.350\nsteps_remaining: 13\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~14 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 139.44", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.636\nqueue_backlog: 0.000\nsystem_latency: 0.300\nstep_count: 0.400\nsteps_remaining: 12\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 150.13", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.591\nqueue_backlog: 0.000\nsystem_latency: 0.407\nstep_count: 0.450\nsteps_remaining: 11\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~13 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 203.27", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.545\nqueue_backlog: 0.000\nsystem_latency: 0.247\nstep_count: 0.500\nsteps_remaining: 10\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~11 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 123.75", "role": "user"}, {"content": "route_to_b", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.500\nbudget_remaining: 0.500\nqueue_backlog: 0.000\nsystem_latency: 0.374\nstep_count: 0.550\nsteps_remaining: 9\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: ~10 steps\nprevious_step_feedback:\n previous_action: route_to_b\n previous_reward: +0.77\n previous_success: true\n previous_cost: 0.05\n previous_latency_ms: 187.04", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.409\nqueue_backlog: 0.000\nsystem_latency: 0.318\nstep_count: 0.600\nsteps_remaining: 8\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~4 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 158.76", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.494\nstep_count: 0.650\nsteps_remaining: 7\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.250\nbudget_runway_at_current_rate: ~3 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 247.20", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 1.000\nbudget_remaining: 0.318\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.700\nsteps_remaining: 6\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.667\nbudget_remaining: 0.227\nqueue_backlog: 0.200\nsystem_latency: 0.879\nstep_count: 0.750\nsteps_remaining: 5\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:-0.167\nbudget_runway_at_current_rate: ~2 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: -2.45\n previous_success: false\n previous_cost: 0.10\n previous_latency_ms: 439.64", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.667\nbudget_remaining: 0.227\nqueue_backlog: 0.100\nsystem_latency: 0.000\nstep_count: 0.800\nsteps_remaining: 4\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:-0.167\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "route_to_c", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.750\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.431\nstep_count: 0.850\nsteps_remaining: 3\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.042\nbudget_runway_at_current_rate: ~1 steps\nprevious_step_feedback:\n previous_action: route_to_c\n previous_reward: +0.55\n previous_success: true\n previous_cost: 0.10\n previous_latency_ms: 215.67", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.750\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.900\nsteps_remaining: 2\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.042\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}, {"content": "task: hard_multi\nprovider_a_status: 0.500\nprovider_b_status: 1.000\nprovider_c_status: 0.750\nbudget_remaining: 0.136\nqueue_backlog: 0.000\nsystem_latency: 0.000\nstep_count: 0.950\nsteps_remaining: 1\ntrend (avg/step, 2-step): A:+0.000 B:+0.000 C:+0.000\nbudget_runway_at_current_rate: >20 steps\nprevious_step_feedback:\n previous_action: shed_load\n previous_reward: -0.50\n previous_success: false\n previous_cost: 0.00\n previous_latency_ms: 0.00", "role": "user"}, {"content": "shed_load", "role": "assistant"}], "seed": 1066, "teacher": "ppo", "teacher_score": 0.6872} diff --git a/outputs/trace_heuristic_hard_multi_seed3.json b/outputs/trace_heuristic_hard_multi_seed3.json new file mode 100644 index 0000000000000000000000000000000000000000..2cc01fa365be48d4954656b85a4413d6dbab1a5b --- /dev/null +++ b/outputs/trace_heuristic_hard_multi_seed3.json @@ -0,0 +1,425 @@ +{ + "task": "hard_multi", + "seed": 3, + "policy": "heuristic", + "episode_length": 20, + "total_reward": -1.9899, + "grader": { + "overall_score": 0.6289, + "success_score": 0.7, + "latency_score": 0.5416, + "budget_score": 0.2091, + "sla_score": 0.95, + "adaptation_score": 0.6833 + }, + "metrics": { + "total_reward": -1.9895, + "success_rate": 0.7, + "total_cost_spent": 0.87, + "average_latency_ms": 229.18, + "sla_met": false, + "queue_overflow_events": 0 + }, + "steps": [ + { + "step": 1, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 0.9545, + "cost": 0.01, + "budget_remaining": 1.09, + "latency_ms": 86.82, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 1.0, + "queue_backlog": 0.0, + "system_latency": 0.2, + "step_count": 0.0 + }, + { + "step": 2, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 1.9091, + "cost": 0.01, + "budget_remaining": 1.08, + "latency_ms": 86.92, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9909, + "queue_backlog": 0.0, + "system_latency": 0.1736, + "step_count": 0.05 + }, + { + "step": 3, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 2.8636, + "cost": 0.01, + "budget_remaining": 1.07, + "latency_ms": 66.96, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9818, + "queue_backlog": 0.0, + "system_latency": 0.1738, + "step_count": 0.1 + }, + { + "step": 4, + "action": "route_to_a", + "provider": "A", + "success": false, + "reward": -2.0455, + "cumulative_reward": 0.8182, + "cost": 0.01, + "budget_remaining": 1.06, + "latency_ms": 301.54, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9727, + "queue_backlog": 0.0, + "system_latency": 0.1339, + "step_count": 0.15 + }, + { + "step": 5, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 1.7727, + "cost": 0.01, + "budget_remaining": 1.05, + "latency_ms": 121.62, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.75, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9636, + "queue_backlog": 0.2, + "system_latency": 0.6031, + "step_count": 0.2 + }, + { + "step": 6, + "action": "route_to_a", + "provider": "A", + "success": false, + "reward": -2.0455, + "cumulative_reward": -0.2727, + "cost": 0.01, + "budget_remaining": 1.04, + "latency_ms": 394.2, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.8, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9545, + "queue_backlog": 0.1, + "system_latency": 0.2432, + "step_count": 0.25 + }, + { + "step": 7, + "action": "route_to_a", + "provider": "A", + "success": false, + "reward": -2.0455, + "cumulative_reward": -2.3182, + "cost": 0.01, + "budget_remaining": 1.03, + "latency_ms": 430.66, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9455, + "queue_backlog": 0.3, + "system_latency": 0.7884, + "step_count": 0.3 + }, + { + "step": 8, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": -1.5455, + "cost": 0.05, + "budget_remaining": 0.98, + "latency_ms": 104.03, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9364, + "queue_backlog": 0.5, + "system_latency": 0.8613, + "step_count": 0.35 + }, + { + "step": 9, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": -0.7727, + "cost": 0.05, + "budget_remaining": 0.93, + "latency_ms": 203.27, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8909, + "queue_backlog": 0.4, + "system_latency": 0.2081, + "step_count": 0.4 + }, + { + "step": 10, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 0.0, + "cost": 0.05, + "budget_remaining": 0.88, + "latency_ms": 56.63, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8455, + "queue_backlog": 0.3, + "system_latency": 0.4065, + "step_count": 0.45 + }, + { + "step": 11, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 0.7727, + "cost": 0.05, + "budget_remaining": 0.83, + "latency_ms": 43.91, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8, + "queue_backlog": 0.2, + "system_latency": 0.1133, + "step_count": 0.5 + }, + { + "step": 12, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 1.5455, + "cost": 0.05, + "budget_remaining": 0.78, + "latency_ms": 141.1, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7545, + "queue_backlog": 0.1, + "system_latency": 0.0878, + "step_count": 0.55 + }, + { + "step": 13, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 2.3182, + "cost": 0.05, + "budget_remaining": 0.73, + "latency_ms": 120.39, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7091, + "queue_backlog": 0.0, + "system_latency": 0.2822, + "step_count": 0.6 + }, + { + "step": 14, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 3.0909, + "cost": 0.05, + "budget_remaining": 0.68, + "latency_ms": 147.75, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6636, + "queue_backlog": 0.0, + "system_latency": 0.2408, + "step_count": 0.65 + }, + { + "step": 15, + "action": "route_to_b", + "provider": "B", + "success": false, + "reward": -2.2273, + "cumulative_reward": 0.8636, + "cost": 0.05, + "budget_remaining": 0.63, + "latency_ms": 397.66, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6182, + "queue_backlog": 0.0, + "system_latency": 0.2955, + "step_count": 0.7 + }, + { + "step": 16, + "action": "route_to_b", + "provider": "B", + "success": false, + "reward": -2.2622, + "cumulative_reward": -1.3986, + "cost": 0.05, + "budget_remaining": 0.58, + "latency_ms": 517.46, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.8, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5727, + "queue_backlog": 0.2, + "system_latency": 0.7953, + "step_count": 0.75 + }, + { + "step": 17, + "action": "route_to_b", + "provider": "B", + "success": false, + "reward": -2.2273, + "cumulative_reward": -3.6258, + "cost": 0.05, + "budget_remaining": 0.53, + "latency_ms": 484.31, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.6, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5273, + "queue_backlog": 0.4, + "system_latency": 1.0, + "step_count": 0.8 + }, + { + "step": 18, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": -3.0804, + "cost": 0.1, + "budget_remaining": 0.43, + "latency_ms": 216.39, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.4, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.4818, + "queue_backlog": 0.6, + "system_latency": 0.9686, + "step_count": 0.85 + }, + { + "step": 19, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": -2.5349, + "cost": 0.1, + "budget_remaining": 0.33, + "latency_ms": 365.02, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.4, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.3909, + "queue_backlog": 0.5, + "system_latency": 0.4328, + "step_count": 0.9 + }, + { + "step": 20, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": -1.9895, + "cost": 0.1, + "budget_remaining": 0.23, + "latency_ms": 296.88, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.4, + "provider_b_status": 0.4, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.3, + "queue_backlog": 0.4, + "system_latency": 0.73, + "step_count": 0.95 + } + ] +} diff --git a/outputs/trace_llm_hard_multi_seed3.json b/outputs/trace_llm_hard_multi_seed3.json new file mode 100644 index 0000000000000000000000000000000000000000..383826f305edec36369b027cbfdd58d4e18fab9c --- /dev/null +++ b/outputs/trace_llm_hard_multi_seed3.json @@ -0,0 +1,425 @@ +{ + "task": "hard_multi", + "seed": 3, + "policy": "llm", + "episode_length": 20, + "total_reward": 3.6011, + "grader": { + "overall_score": 0.6814, + "success_score": 0.8, + "latency_score": 0.6102, + "budget_score": 0.1273, + "sla_score": 0.95, + "adaptation_score": 0.7889 + }, + "metrics": { + "total_reward": 3.6014, + "success_rate": 0.8, + "total_cost_spent": 0.96, + "average_latency_ms": 194.89, + "sla_met": false, + "queue_overflow_events": 0 + }, + "steps": [ + { + "step": 1, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 0.9545, + "cost": 0.01, + "budget_remaining": 1.09, + "latency_ms": 86.82, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 1.0, + "queue_backlog": 0.0, + "system_latency": 0.2, + "step_count": 0.0 + }, + { + "step": 2, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 1.9091, + "cost": 0.01, + "budget_remaining": 1.08, + "latency_ms": 86.92, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9909, + "queue_backlog": 0.0, + "system_latency": 0.1736, + "step_count": 0.05 + }, + { + "step": 3, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 2.8636, + "cost": 0.01, + "budget_remaining": 1.07, + "latency_ms": 66.96, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9818, + "queue_backlog": 0.0, + "system_latency": 0.1738, + "step_count": 0.1 + }, + { + "step": 4, + "action": "route_to_a", + "provider": "A", + "success": false, + "reward": -2.0455, + "cumulative_reward": 0.8182, + "cost": 0.01, + "budget_remaining": 1.06, + "latency_ms": 301.54, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 1.0, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9727, + "queue_backlog": 0.0, + "system_latency": 0.1339, + "step_count": 0.15 + }, + { + "step": 5, + "action": "route_to_a", + "provider": "A", + "success": true, + "reward": 0.9545, + "cumulative_reward": 1.7727, + "cost": 0.01, + "budget_remaining": 1.05, + "latency_ms": 121.62, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.75, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9636, + "queue_backlog": 0.2, + "system_latency": 0.6031, + "step_count": 0.2 + }, + { + "step": 6, + "action": "route_to_a", + "provider": "A", + "success": false, + "reward": -2.0455, + "cumulative_reward": -0.2727, + "cost": 0.01, + "budget_remaining": 1.04, + "latency_ms": 394.2, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.8, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9545, + "queue_backlog": 0.1, + "system_latency": 0.2432, + "step_count": 0.25 + }, + { + "step": 7, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 0.5, + "cost": 0.05, + "budget_remaining": 0.99, + "latency_ms": 86.84, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9455, + "queue_backlog": 0.3, + "system_latency": 0.7884, + "step_count": 0.3 + }, + { + "step": 8, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 1.2727, + "cost": 0.05, + "budget_remaining": 0.94, + "latency_ms": 91.54, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9, + "queue_backlog": 0.2, + "system_latency": 0.1737, + "step_count": 0.35 + }, + { + "step": 9, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 2.0455, + "cost": 0.05, + "budget_remaining": 0.89, + "latency_ms": 177.86, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8545, + "queue_backlog": 0.1, + "system_latency": 0.1831, + "step_count": 0.4 + }, + { + "step": 10, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 2.8182, + "cost": 0.05, + "budget_remaining": 0.84, + "latency_ms": 49.24, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8091, + "queue_backlog": 0.0, + "system_latency": 0.3557, + "step_count": 0.45 + }, + { + "step": 11, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 3.5909, + "cost": 0.05, + "budget_remaining": 0.79, + "latency_ms": 39.92, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7636, + "queue_backlog": 0.0, + "system_latency": 0.0985, + "step_count": 0.5 + }, + { + "step": 12, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 4.3636, + "cost": 0.05, + "budget_remaining": 0.74, + "latency_ms": 134.38, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7182, + "queue_backlog": 0.0, + "system_latency": 0.0798, + "step_count": 0.55 + }, + { + "step": 13, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 5.1364, + "cost": 0.05, + "budget_remaining": 0.69, + "latency_ms": 120.39, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6727, + "queue_backlog": 0.0, + "system_latency": 0.2688, + "step_count": 0.6 + }, + { + "step": 14, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 5.9091, + "cost": 0.05, + "budget_remaining": 0.64, + "latency_ms": 147.75, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6273, + "queue_backlog": 0.0, + "system_latency": 0.2408, + "step_count": 0.65 + }, + { + "step": 15, + "action": "route_to_b", + "provider": "B", + "success": false, + "reward": -2.2273, + "cumulative_reward": 3.6818, + "cost": 0.05, + "budget_remaining": 0.59, + "latency_ms": 397.66, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5818, + "queue_backlog": 0.0, + "system_latency": 0.2955, + "step_count": 0.7 + }, + { + "step": 16, + "action": "route_to_b", + "provider": "B", + "success": false, + "reward": -2.2622, + "cumulative_reward": 1.4196, + "cost": 0.05, + "budget_remaining": 0.54, + "latency_ms": 517.46, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.8, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5364, + "queue_backlog": 0.2, + "system_latency": 0.7953, + "step_count": 0.75 + }, + { + "step": 17, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 1.9651, + "cost": 0.1, + "budget_remaining": 0.44, + "latency_ms": 304.31, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.6, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.4909, + "queue_backlog": 0.4, + "system_latency": 1.0, + "step_count": 0.8 + }, + { + "step": 18, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 2.5105, + "cost": 0.1, + "budget_remaining": 0.34, + "latency_ms": 191.42, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.6, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.4, + "queue_backlog": 0.3, + "system_latency": 0.6086, + "step_count": 0.85 + }, + { + "step": 19, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 3.056, + "cost": 0.1, + "budget_remaining": 0.24, + "latency_ms": 321.21, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.6, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.3091, + "queue_backlog": 0.2, + "system_latency": 0.3828, + "step_count": 0.9 + }, + { + "step": 20, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 3.6014, + "cost": 0.1, + "budget_remaining": 0.14, + "latency_ms": 259.77, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.6, + "provider_b_status": 0.6, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.2182, + "queue_backlog": 0.1, + "system_latency": 0.6424, + "step_count": 0.95 + } + ] +} diff --git a/outputs/trace_ppo_hard_multi_seed3.json b/outputs/trace_ppo_hard_multi_seed3.json new file mode 100644 index 0000000000000000000000000000000000000000..82d8b380f9c76866041fdf7c0e56f46bdad18984 --- /dev/null +++ b/outputs/trace_ppo_hard_multi_seed3.json @@ -0,0 +1,425 @@ +{ + "task": "hard_multi", + "seed": 3, + "policy": "ppo", + "episode_length": 20, + "total_reward": 9.4544, + "grader": { + "overall_score": 0.7514, + "success_score": 0.8, + "latency_score": 0.7388, + "budget_score": 0.0909, + "sla_score": 1.0, + "adaptation_score": 1.0 + }, + "metrics": { + "total_reward": 9.4545, + "success_rate": 1.0, + "total_cost_spent": 1.0, + "average_latency_ms": 130.62, + "sla_met": true, + "queue_overflow_events": 0 + }, + "steps": [ + { + "step": 1, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 0.7727, + "cost": 0.05, + "budget_remaining": 1.05, + "latency_ms": 136.82, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 0.5, + "provider_c_status": 0.5, + "observed_budget_remaining": 1.0, + "queue_backlog": 0.0, + "system_latency": 0.2, + "step_count": 0.0 + }, + { + "step": 2, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 1.5455, + "cost": 0.05, + "budget_remaining": 1.0, + "latency_ms": 136.92, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9545, + "queue_backlog": 0.0, + "system_latency": 0.2736, + "step_count": 0.05 + }, + { + "step": 3, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 2.3182, + "cost": 0.05, + "budget_remaining": 0.95, + "latency_ms": 116.96, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.9091, + "queue_backlog": 0.0, + "system_latency": 0.2738, + "step_count": 0.1 + }, + { + "step": 4, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 3.0909, + "cost": 0.05, + "budget_remaining": 0.9, + "latency_ms": 148.46, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8636, + "queue_backlog": 0.0, + "system_latency": 0.2339, + "step_count": 0.15 + }, + { + "step": 5, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 3.8636, + "cost": 0.05, + "budget_remaining": 0.85, + "latency_ms": 160.57, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.8182, + "queue_backlog": 0.0, + "system_latency": 0.2969, + "step_count": 0.2 + }, + { + "step": 6, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 4.6364, + "cost": 0.05, + "budget_remaining": 0.8, + "latency_ms": 74.57, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7727, + "queue_backlog": 0.0, + "system_latency": 0.3211, + "step_count": 0.25 + }, + { + "step": 7, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 5.4091, + "cost": 0.05, + "budget_remaining": 0.75, + "latency_ms": 75.52, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.7273, + "queue_backlog": 0.0, + "system_latency": 0.1491, + "step_count": 0.3 + }, + { + "step": 8, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 6.1818, + "cost": 0.05, + "budget_remaining": 0.7, + "latency_ms": 83.22, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6818, + "queue_backlog": 0.0, + "system_latency": 0.151, + "step_count": 0.35 + }, + { + "step": 9, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 6.9545, + "cost": 0.05, + "budget_remaining": 0.65, + "latency_ms": 169.39, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.6364, + "queue_backlog": 0.0, + "system_latency": 0.1664, + "step_count": 0.4 + }, + { + "step": 10, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 7.7273, + "cost": 0.05, + "budget_remaining": 0.6, + "latency_ms": 49.24, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5909, + "queue_backlog": 0.0, + "system_latency": 0.3388, + "step_count": 0.45 + }, + { + "step": 11, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 8.5, + "cost": 0.05, + "budget_remaining": 0.55, + "latency_ms": 39.92, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5455, + "queue_backlog": 0.0, + "system_latency": 0.0985, + "step_count": 0.5 + }, + { + "step": 12, + "action": "route_to_b", + "provider": "B", + "success": true, + "reward": 0.7727, + "cumulative_reward": 9.2727, + "cost": 0.05, + "budget_remaining": 0.5, + "latency_ms": 134.38, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.5, + "queue_backlog": 0.0, + "system_latency": 0.0798, + "step_count": 0.55 + }, + { + "step": 13, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 9.8182, + "cost": 0.1, + "budget_remaining": 0.4, + "latency_ms": 170.39, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 0.5, + "observed_budget_remaining": 0.4545, + "queue_backlog": 0.0, + "system_latency": 0.2688, + "step_count": 0.6 + }, + { + "step": 14, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 10.3636, + "cost": 0.1, + "budget_remaining": 0.3, + "latency_ms": 197.75, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.3636, + "queue_backlog": 0.0, + "system_latency": 0.3408, + "step_count": 0.65 + }, + { + "step": 15, + "action": "shed_load", + "provider": null, + "success": false, + "reward": -0.5, + "cumulative_reward": 9.8636, + "cost": 0.0, + "budget_remaining": 0.3, + "latency_ms": 0.0, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.2727, + "queue_backlog": 0.0, + "system_latency": 0.3955, + "step_count": 0.7 + }, + { + "step": 16, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 10.4091, + "cost": 0.1, + "budget_remaining": 0.2, + "latency_ms": 295.11, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.2727, + "queue_backlog": 0.0, + "system_latency": 0.0, + "step_count": 0.75 + }, + { + "step": 17, + "action": "shed_load", + "provider": null, + "success": false, + "reward": -0.5, + "cumulative_reward": 9.9091, + "cost": 0.0, + "budget_remaining": 0.2, + "latency_ms": 0.0, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.1818, + "queue_backlog": 0.0, + "system_latency": 0.5902, + "step_count": 0.8 + }, + { + "step": 18, + "action": "route_to_c", + "provider": "C", + "success": true, + "reward": 0.5455, + "cumulative_reward": 10.4545, + "cost": 0.1, + "budget_remaining": 0.1, + "latency_ms": 100.73, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.1818, + "queue_backlog": 0.0, + "system_latency": 0.0, + "step_count": 0.85 + }, + { + "step": 19, + "action": "shed_load", + "provider": null, + "success": false, + "reward": -0.5, + "cumulative_reward": 9.9545, + "cost": 0.0, + "budget_remaining": 0.1, + "latency_ms": 0.0, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.0909, + "queue_backlog": 0.0, + "system_latency": 0.2015, + "step_count": 0.9 + }, + { + "step": 20, + "action": "shed_load", + "provider": null, + "success": false, + "reward": -0.5, + "cumulative_reward": 9.4545, + "cost": 0.0, + "budget_remaining": 0.1, + "latency_ms": 0.0, + "queue_overflow": false, + "budget_exhausted": false, + "provider_a_status": 0.5, + "provider_b_status": 1.0, + "provider_c_status": 1.0, + "observed_budget_remaining": 0.0909, + "queue_backlog": 0.0, + "system_latency": 0.0, + "step_count": 0.95 + } + ] +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..a3f681428590910196d6446ea2547a457098d8da --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[project] +name = "budget-router" +version = "0.1.0" +description = "Incident Commander for Budgeted Tool/API Reliability — OpenEnv RL Environment" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "Apache-2.0" } +authors = [ + { name = "Akshay Babbar" }, + { name = "Harshit Babbar" }, +] +dependencies = [ + "fastapi>=0.110,<1.0", + "gradio>=5.0,<6.0", + "pydantic>=2.0,<3.0", + "openenv-core>=0.1.0", + "openai>=1.0,<2.0", + "requests>=2.31,<3.0", + "starlette<1.0", + "typer>=0.12,<1.0", + "uvicorn>=0.30,<1.0", + "jmespath>=1.1.0", + "huggingface-hub>=1.12.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov>=4.0", +] +training = [ + "gymnasium>=1.0.0,<2.0", + "stable-baselines3>=2.0,<3.0", +] +grpo = [ + "torch>=2.1.0", + "transformers>=5.3.0", + "huggingface_hub>=1.5.0", + "trl>=0.15.0", + "peft>=0.10.0", + "accelerate>=0.30.0", + "datasets>=2.18.0", + "bitsandbytes>=0.43.0", +] + +[project.scripts] +server = "server.app:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pytest.ini_options] +testpaths = ["budget_router/tests"] +pythonpath = ["."] + +[dependency-groups] +dev = [ + "pytest>=9.0.2", + "python-docx>=1.2.0", +] + diff --git a/scripts/hf_export_build_commit.py b/scripts/hf_export_build_commit.py new file mode 100755 index 0000000000000000000000000000000000000000..8229a3479038e48e3f03de96ae65b8b42c683324 --- /dev/null +++ b/scripts/hf_export_build_commit.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +Build a *new* commit (orphan) from the current `HEAD` tree, omitting: + - any binary blob (HF rejects plain-Git binaries unless Xet is used) + - any blob with size > MAX_BYTES (default 10 MiB) + - any subtree whose *sum* of **remaining** blob sizes under a directory prefix exceeds MAX_BYTES + (repeatedly removes the shortest over-budget prefix first) + +Prints the new commit object id (40 hex) to stdout. By default, stderr prints a one-line +omit summary; set QUIET=1 to suppress it or VERBOSE=1 to list omitted paths. + +Run from the repository root (or any path inside the repo). Uses a temporary +GIT_INDEX_FILE; does not modify the working tree or the current branch. +""" +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile +from collections import defaultdict +from dataclasses import dataclass +@dataclass(frozen=True) +class Blob: + path: str + size: int + mode: str + oid: str + + +def sh(args: list[str], cwd: str, env: dict[str, str] | None = None) -> str: + menv = {**os.environ, "LC_ALL": "C"} if env is None else env + return ( + subprocess.check_output( + args, + cwd=cwd, + stderr=subprocess.DEVNULL, + env=menv, + ).decode("utf-8", "replace") + ).strip() + + +def list_blobs(cwd: str) -> list[Blob]: + out = sh(["git", "ls-tree", "-l", "-r", "HEAD"], cwd=cwd) + blobs: list[Blob] = [] + for line in out.splitlines(): + if "\t" not in line: + continue + meta, path = line.split("\t", 1) + toks = meta.split() + if len(toks) < 4: + continue + mode, otype, _oid, size_s = toks[0], toks[1], toks[2], toks[3] + if otype == "commit": + continue + if otype != "blob": + continue + blobs.append(Blob(path=path, size=int(size_s), mode=mode, oid=toks[2])) + return blobs + + +def is_binary_blob(cwd: str, oid: str) -> bool: + data = subprocess.check_output( + ["git", "cat-file", "blob", oid], + cwd=cwd, + stderr=subprocess.DEVNULL, + )[:8192] + if b"\0" in data: + return True + try: + data.decode("utf-8") + except UnicodeDecodeError: + return True + return False + + +def parent_prefixes(path: str) -> list[str]: + if "/" not in path: + return [] + parts = path.split("/") + return ["/".join(parts[:i]) for i in range(1, len(parts))] + + +def apply_rules( + cwd: str, + blobs: list[Blob], + max_b: int, + *, + verbose: bool, +) -> tuple[dict[str, Blob], dict[str, int]]: + """Return keep map and omitted counts by reason.""" + keep: dict[str, Blob] = {} + omitted = {"binary": 0, "file": 0, "subtree": 0} + for b in blobs: + if b.size > max_b: + omitted["file"] += 1 + if verbose: + print(f"Omit: file {b.path!r} ({b.size} B) > {max_b} B", file=sys.stderr) + elif is_binary_blob(cwd, b.oid): + omitted["binary"] += 1 + if verbose: + print(f"Omit: binary file {b.path!r}", file=sys.stderr) + else: + keep[b.path] = b + + while True: + psum: dict[str, int] = defaultdict(int) + for p, b in keep.items(): + for d in parent_prefixes(p): + psum[d] += b.size + bad = [d for d, t in psum.items() if t > max_b] + if not bad: + break + victim = min(bad, key=lambda d: (len(d.split("/")), d)) + to_del = [p for p in keep if p == victim or p.startswith(victim + "/")] + for p in to_del: + del keep[p] + omitted["subtree"] += 1 + if verbose: + print( + f"Omit: subtree {victim!r} (sum of kept blobs under prefix > {max_b} B)", + file=sys.stderr, + ) + + return keep, omitted + + +def make_export_commit(cwd: str, paths: list[str]) -> str: + if not paths: + raise SystemExit("error: nothing to commit after size filter (empty tree)") + + empty = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" # empty tree object + with tempfile.TemporaryDirectory() as tdir: + gidx = os.path.join(tdir, "i") + env: dict[str, str] = {**os.environ, "GIT_INDEX_FILE": gidx} + subprocess.check_call( + ["git", "read-tree", empty], cwd=cwd, env=env, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL + ) + for p in paths: + r = subprocess.run( + ["git", "ls-tree", "HEAD", "--", p], + cwd=cwd, + env=env, + capture_output=True, + text=True, + ) + if r.returncode != 0 or not r.stdout.strip(): + continue + line = r.stdout.strip() + mline, path_f = line.split("\t", 1) + p_use = path_f if path_f else p + toks = mline.split() + if len(toks) < 3: + continue + mode, otype, oid = toks[0], toks[1], toks[2] + if otype != "blob": + continue + subprocess.check_call( + ["git", "update-index", "--add", "--cacheinfo", f"{mode},{oid},{p_use}"], + cwd=cwd, + env=env, + stdout=subprocess.DEVNULL, + ) + tree = subprocess.check_output(["git", "write-tree"], cwd=cwd, env=env).decode().strip() + msg = os.environ.get("HF_EXPORT_MSG", "chore: HF Space export (size filter)") + commit = sh(["git", "commit-tree", tree, "-m", msg], cwd=cwd, env=env) + if len(commit) < 4: + raise SystemExit("error: could not build export commit") + return commit + + +def main() -> None: + top = sh(["git", "rev-parse", "--show-toplevel"], cwd=os.path.abspath(".")) + if not top: + raise SystemExit("error: not a git repository") + cwd = top + max_b = int(os.environ.get("MAX_BYTES", str(10 * 1024 * 1024))) + verbose = os.environ.get("VERBOSE", "").lower() in ("1", "true", "yes", "y") + quiet = os.environ.get("QUIET", "0").lower() in ("1", "true", "yes", "y") + + blobs = list_blobs(cwd) + _keep, omitted = apply_rules(cwd, blobs, max_b, verbose=verbose) + paths = sorted(_keep) + n_om = sum(omitted.values()) + if not quiet and n_om: + print( + "HF export: omitted " + f"{n_om} path(s) " + f"(binary={omitted['binary']}, file>{max_b}B={omitted['file']}, " + f"subtree>{max_b}B={omitted['subtree']})", + file=sys.stderr, + ) + + oid = make_export_commit(cwd, paths) + print(oid, end="") + + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as e: # pragma: no cover + print("error: git command failed", file=sys.stderr) + raise SystemExit(1) from e diff --git a/scripts/push_to_hf.sh b/scripts/push_to_hf.sh new file mode 100755 index 0000000000000000000000000000000000000000..19051dd63b91fce5e05628b4efdc274fad38dfcc --- /dev/null +++ b/scripts/push_to_hf.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Push to a Hugging Face Space as branch "main" (force). +# +# By default we do **not** push your current HEAD as-is. We build a **new** commit (no local +# branch change) that **drops** anything that would exceed the Hub / your size policy: +# - any binary file (plain Git binaries are rejected by Hugging Face without Xet) +# - any file (blob) > MAX_BYTES (default 10 MiB) +# - any directory *prefix* whose **sum of kept** blob sizes would exceed MAX_BYTES; the +# whole subtree is dropped +# This is the only way to "silently skip" content that is *already* in your git history: a +# normal `git push` would still send old blobs. See scripts/hf_export_build_commit.py. +# Default stderr prints a one-line omit count; set QUIET=1 to suppress it, VERBOSE=1 +# to print each omitted path. MAX_BYTES is honored by the Python step. +# +# Environment: +# HF_TOKEN — required; write token +# SNAPSHOT_COMMIT=1 — `git add -A`, commit (after unstaging single files over MAX_BYTES on +# disk, still respects .gitignore), then export+push +# SKIP_HF_FILTER=1 — push **HEAD** without building the size-filtered export (same as old +# "push whole branch"; large blobs in history are still sent) +# QUIET, VERBOSE, MAX_BYTES, HF_USER, PYTHON +set -euo pipefail + +NAMESPACE="${1:-}" +SPACE_NAME="${2:-}" +: "${NAMESPACE:?usage: $0 }" +: "${SPACE_NAME:?usage: $0 }" +: "${HF_TOKEN:?set HF_TOKEN to a Hugging Face write token}" + +HF_USER="${HF_USER:-$NAMESPACE}" +MAX_BYTES="${MAX_BYTES:-$((10 * 1024 * 1024))}" +PYTHON="${PYTHON:-python3}" +ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { + echo "error: not inside a git repository" >&2 + exit 1 +} +cd "$ROOT" + +unstage_staged_file_over_max() { + local f s + while IFS= read -r f; do + [ -n "$f" ] || continue + s=0 + if [ -L "$f" ] || ( [ -e "$f" ] && [ ! -d "$f" ] ); then + s=$(stat -f%z -- "$f" 2>/dev/null) || s=0 + fi + if [ -n "$s" ] && [ "$s" -gt "$MAX_BYTES" ] 2>/dev/null; then + git reset -q HEAD -- "$f" 2>/dev/null || true + fi + done < <(git diff --cached --name-only 2>/dev/null) +} + +if [[ "${SNAPSHOT_COMMIT:-0}" == "1" ]]; then + git add -A + unstage_staged_file_over_max + if git diff --cached --quiet; then + echo "SNAPSHOT_COMMIT: nothing to commit" >&2 + else + git commit -m "chore: snapshot before push to Hugging Face Space" + fi +fi + +if [[ "${SKIP_HF_FILTER:-0}" == "1" ]]; then + PUBLISH_REF="HEAD" +else + if ! command -v "$PYTHON" &>/dev/null; then + echo "error: $PYTHON is required to build the HF size-filtered export" >&2 + exit 1 + fi + if [[ ! -f "$ROOT/scripts/hf_export_build_commit.py" ]]; then + echo "error: missing $ROOT/scripts/hf_export_build_commit.py" >&2 + exit 1 + fi + export MAX_BYTES + PUBLISH_REF=$("$PYTHON" "$ROOT/scripts/hf_export_build_commit.py") + if [ "${#PUBLISH_REF}" -ne 40 ]; then + echo "error: export commit not produced" >&2 + exit 1 + fi +fi + +REMOTE="https://${HF_USER}:${HF_TOKEN}@huggingface.co/spaces/${NAMESPACE}/${SPACE_NAME}" +FSTATE="on" +if [[ "${SKIP_HF_FILTER:-0}" == "1" ]]; then + FSTATE="off (HEAD as-is)" +fi +echo "Pushing ${PUBLISH_REF} (size filter: ${FSTATE}) -> huggingface.co/${NAMESPACE}/${SPACE_NAME} main" +git push --force "$REMOTE" "${PUBLISH_REF}:main" diff --git a/scripts/run_colab_grpo.sh b/scripts/run_colab_grpo.sh new file mode 100755 index 0000000000000000000000000000000000000000..aa1dba7546346f5f82151382e75ebc89a1af5699 --- /dev/null +++ b/scripts/run_colab_grpo.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Canonical Google Colab launcher for the GRPO Budget Router experiment. +# Run from the repository root after cloning: +# +# bash scripts/run_colab_grpo.sh +# +# Optional overrides: +# MODEL_NAME=Qwen/Qwen3-0.6B MAX_STEPS=30 bash scripts/run_colab_grpo.sh + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +MODEL_NAME="${MODEL_NAME:-Qwen/Qwen3-1.7B}" +MAX_STEPS="${MAX_STEPS:-60}" +DATASET_N="${DATASET_N:-64}" +NUM_GENERATIONS="${NUM_GENERATIONS:-8}" +TEMPERATURE="${TEMPERATURE:-1.2}" +TOP_P="${TOP_P:-0.95}" +PROMPT_STYLE="${PROMPT_STYLE:-explore}" +MAX_COMPLETION_LENGTH="${MAX_COMPLETION_LENGTH:-3500}" +SAVE_STEPS="${SAVE_STEPS:-1000}" +LOG_DIR="${LOG_DIR:-outputs}" + +mkdir -p "$LOG_DIR" .colab_runtime + +if ! command -v uv >/dev/null 2>&1; then + python -m pip install -q uv +fi + +uv sync --extra grpo --extra training --extra dev + +echo "== GPU / dtype check ==" +CUDA_BF16_SUPPORTED="$( + uv run python - <<'PY' +import torch +print(bool(torch.cuda.is_available() and torch.cuda.is_bf16_supported())) +PY +)" + +TRAIN_SCRIPT="train/learn_experiment.py" +if [[ "$CUDA_BF16_SUPPORTED" != "True" ]]; then + TRAIN_SCRIPT=".colab_runtime/learn_experiment_colab.py" + uv run python - <<'PY' +from pathlib import Path + +src = Path("train/learn_experiment.py") +dst = Path(".colab_runtime/learn_experiment_colab.py") +text = src.read_text() +old = ' dtype = torch.bfloat16 if device in ("mps", "cuda") else torch.float32' +new = ''' dtype = ( + torch.bfloat16 + if device == "mps" or (device == "cuda" and torch.cuda.is_bf16_supported()) + else torch.float16 + if device == "cuda" + else torch.float32 + )''' +if old not in text: + raise SystemExit("Expected dtype line not found; refusing to patch temporary Colab trainer.") +dst.write_text(text.replace(old, new)) +print(f"Using temporary Colab-safe trainer: {dst}") +PY +else + echo "CUDA bf16 is supported; using canonical train/learn_experiment.py directly." +fi + +STAMP="$(date +%Y%m%d_%H%M%S)" +SAFE_MODEL_NAME="${MODEL_NAME//\//_}" +LOG_FILE="$LOG_DIR/grpo_colab_${SAFE_MODEL_NAME}_steps${MAX_STEPS}_${STAMP}.log" + +echo "== Launching GRPO ==" +echo "model=$MODEL_NAME steps=$MAX_STEPS generations=$NUM_GENERATIONS max_completion_length=$MAX_COMPLETION_LENGTH" +echo "log=$LOG_FILE" + +PYTORCH_ENABLE_MPS_FALLBACK=1 uv run python "$TRAIN_SCRIPT" \ + --model-name "$MODEL_NAME" \ + --max-steps "$MAX_STEPS" \ + --dataset-n "$DATASET_N" \ + --save-steps "$SAVE_STEPS" \ + --num-generations "$NUM_GENERATIONS" \ + --temperature "$TEMPERATURE" \ + --top-p "$TOP_P" \ + --prompt-style "$PROMPT_STYLE" \ + --max-completion-length "$MAX_COMPLETION_LENGTH" \ + 2>&1 | tee "$LOG_FILE" diff --git a/scripts/submit_sft_hf_jobs.sh b/scripts/submit_sft_hf_jobs.sh new file mode 100755 index 0000000000000000000000000000000000000000..dbb0c5e3f5fca5ed2ddc4b4913b3b0e626b70b25 --- /dev/null +++ b/scripts/submit_sft_hf_jobs.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Local data generation reads tokens from the current terminal environment. +# Default teacher is PPO, so this costs 0 large-LLM calls. +: "${HF_TOKEN:?HF_TOKEN must be set in this terminal}" + +export TEACHER_POLICY="${TEACHER_POLICY:-ppo}" +export DATASET_REPO="${DATASET_REPO:-akshay4/budget-router-sft-data}" +export OUTPUT_REPO="${OUTPUT_REPO:-akshay4/budget-router-sft-qwen1.5b}" +export SFT_MODEL_REPO="${SFT_MODEL_REPO:-$OUTPUT_REPO}" +export BASE_MODEL="${BASE_MODEL:-Qwen/Qwen2.5-1.5B-Instruct}" +export TASK_NAME="${TASK_NAME:-hard_multi}" +export SFT_START_SEED="${SFT_START_SEED:-1000}" +export SFT_N_EPISODES="${SFT_N_EPISODES:-100}" +export SFT_TOP_FRACTION="${SFT_TOP_FRACTION:-0.30}" +export SFT_MIN_KEEP="${SFT_MIN_KEEP:-20}" +export SFT_MIN_DELTA="${SFT_MIN_DELTA:-0.0}" +export PPO_MODEL_PATH="${PPO_MODEL_PATH:-trained_models/ppo_hard_multi_100k.zip}" +export NUM_EPOCHS="${NUM_EPOCHS:-3}" +export LORA_R="${LORA_R:-16}" +export LORA_ALPHA="${LORA_ALPHA:-32}" +export LEARNING_RATE="${LEARNING_RATE:-2e-4}" +export MAX_SEQ_LENGTH="${MAX_SEQ_LENGTH:-4096}" +export N_SEEDS="${N_SEEDS:-10}" +export EVAL_SEED_VALUES="${EVAL_SEED_VALUES:-}" +export HF_JOB_FLAVOR="${HF_JOB_FLAVOR:-a10g-large}" +export HF_JOB_NAMESPACE="${HF_JOB_NAMESPACE:-${OUTPUT_REPO%%/*}}" +export TRAIN_TIMEOUT="${TRAIN_TIMEOUT:-2h}" +export EVAL_TIMEOUT="${EVAL_TIMEOUT:-1h}" + +if [[ "${TEACHER_POLICY}" == "llm" ]]; then + calls=$((SFT_N_EPISODES * 20)) + echo "[submit-sft] WARNING: teacher=llm will make up to ${calls} large-model calls." + echo "[submit-sft] Default teacher=ppo avoids this cost." +fi + +if [[ "${SKIP_DATA_GENERATION:-0}" != "1" ]]; then + echo "[submit-sft] Step 1: generating and pushing SFT data locally..." + if [[ "${TEACHER_POLICY}" == "ppo" ]]; then + uv run --extra training --with datasets --with huggingface_hub python generate_sft_data.py + else + uv run --with datasets --with huggingface_hub python generate_sft_data.py + fi +else + echo "[submit-sft] Step 1 skipped: using existing dataset ${DATASET_REPO}" +fi + +echo "[submit-sft] Step 2/3: submitting train and eval as Hugging Face Jobs..." +uv run --with "huggingface_hub>=1.0.0" python - <<'PY' +import os +import time +from huggingface_hub import inspect_job, run_uv_job + + +def wait_for_job(job, timeout_label: str, namespace: str) -> None: + print(f"[submit-sft] job_url={job.url}", flush=True) + while True: + info = inspect_job(job_id=job.id, namespace=namespace) + stage = str(info.status.stage) + print(f"[submit-sft] job={job.id} stage={stage}", flush=True) + if stage in {"COMPLETED", "ERROR", "CANCELED", "DELETED"}: + if stage != "COMPLETED": + raise SystemExit(f"HF Job {job.id} ended with stage={stage}; see {job.url}") + return + time.sleep(30) + + +token = os.environ["HF_TOKEN"] +common_secret = {"HF_TOKEN": token} +flavor = os.environ["HF_JOB_FLAVOR"] +namespace = os.environ["HF_JOB_NAMESPACE"] + +train_args = [ + "--base-model", os.environ["BASE_MODEL"], + "--dataset-repo", os.environ["DATASET_REPO"], + "--output-repo", os.environ["OUTPUT_REPO"], + "--num-epochs", os.environ["NUM_EPOCHS"], + "--learning-rate", os.environ["LEARNING_RATE"], + "--lora-r", os.environ["LORA_R"], + "--lora-alpha", os.environ["LORA_ALPHA"], + "--max-length", os.environ["MAX_SEQ_LENGTH"], +] +print(f"[submit-sft] launching train_sft.py on {flavor}", flush=True) +train_job = run_uv_job( + "train_sft.py", + script_args=train_args, + flavor=flavor, + namespace=namespace, + secrets=common_secret, + timeout=os.environ["TRAIN_TIMEOUT"], +) +wait_for_job(train_job, os.environ["TRAIN_TIMEOUT"], namespace) + +eval_args = [ + "--model-repo", os.environ["SFT_MODEL_REPO"], + "--task", os.environ["TASK_NAME"], + "--n-seeds", os.environ["N_SEEDS"], +] +if os.environ.get("EVAL_SEED_VALUES"): + eval_args.extend(["--seed-values", os.environ["EVAL_SEED_VALUES"]]) + +print(f"[submit-sft] launching eval_sft.py on {flavor}", flush=True) +eval_job = run_uv_job( + "eval_sft.py", + script_args=eval_args, + flavor=flavor, + namespace=namespace, + secrets=common_secret, + timeout=os.environ["EVAL_TIMEOUT"], +) +wait_for_job(eval_job, os.environ["EVAL_TIMEOUT"], namespace) +print(f"[submit-sft] Pipeline complete. Model: {os.environ['OUTPUT_REPO']}", flush=True) +PY diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b17c1e3d73aff424314644cc45fe07a36fea2e64 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,40 @@ +ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest +FROM ${BASE_IMAGE} AS builder + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y --no-install-recommends git curl && \ + rm -rf /var/lib/apt/lists/* + +COPY . /app/env +WORKDIR /app/env + +RUN if ! command -v uv >/dev/null 2>&1; then \ + curl -LsSf https://astral.sh/uv/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx; \ + fi + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --no-install-project --no-editable + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --no-editable + +FROM ${BASE_IMAGE} + +WORKDIR /app + +COPY --from=builder /app/env/.venv /app/.venv +COPY --from=builder /app/env /app/env + +ENV PATH="/app/.venv/bin:$PATH" +ENV PYTHONPATH="/app/env:$PYTHONPATH" + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import os, urllib.request; port = os.environ.get('PORT', '8000'); urllib.request.urlopen(f'http://127.0.0.1:{port}/health', timeout=2)" + +CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port ${PORT:-8000} --proxy-headers --forwarded-allow-ips='*'"] diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/server/app.py b/server/app.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b195bd549736c27865a9c0c516c17bb6bc1a49 --- /dev/null +++ b/server/app.py @@ -0,0 +1,41 @@ +import os + +import uvicorn + +from openenv_core.env_server import create_app, create_fastapi_app + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, Observation + + +try: + import gradio as gr + from app_gradio import build_app +except ImportError: + gr = None + build_app = None + +env = BudgetRouterEnv(emit_structured_logs=True) + +if os.getenv("ENABLE_OPENENV_WEB_INTERFACE", "false").lower() in {"true", "1", "yes"}: + app = create_app(env, Action, Observation) +else: + app = create_fastapi_app(env, Action, Observation) + +if gr is not None and build_app is not None and os.getenv("ENABLE_GRADIO_DASHBOARD", "true").lower() in {"true", "1", "yes"}: + app = gr.mount_gradio_app(app, build_app(), path="/web") + + +def main(host: str = "0.0.0.0", port: int | None = None) -> None: + resolved_port = port or int(os.getenv("PORT", "8000")) + uvicorn.run( + app, + host=host, + port=resolved_port, + proxy_headers=True, + forwarded_allow_ips="*", + ) + + +if __name__ == "__main__": + main() diff --git a/test_docker.sh b/test_docker.sh new file mode 100644 index 0000000000000000000000000000000000000000..74d800b841bca92e6477dc455bd3f006aff8168b --- /dev/null +++ b/test_docker.sh @@ -0,0 +1,5 @@ +docker rm -f br-smoke || true +docker run -d -p 8000:8000 --name br-smoke budget-router-test +sleep 10 +curl -s -X POST http://localhost:8000/reset | python3 -m json.tool +docker stop br-smoke && docker rm br-smoke diff --git a/train/__init__.py b/train/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f1e3c3e539602874d8e4e0fdd127f009d7f59496 --- /dev/null +++ b/train/__init__.py @@ -0,0 +1 @@ +# train package diff --git a/train/eval_hard_multi.py b/train/eval_hard_multi.py new file mode 100644 index 0000000000000000000000000000000000000000..40793c2f0769fe4106ca7d20bbc64e55e67b128f --- /dev/null +++ b/train/eval_hard_multi.py @@ -0,0 +1,204 @@ +""" +Statistical evaluation: PPO vs Heuristic on Hard_Multi. + +This experiment produces the primary quality signal: +- If PPO materially and consistently exceeds the heuristic on Hard_Multi, + it demonstrates the environment contains a learnable signal that reactive + policies cannot exploit — i.e., the environment is high quality. + +- If PPO merely ties or is worse, it suggests the 100k step budget is + insufficient or the signal is too sparse. + +Usage: + uv run python train/eval_hard_multi.py + +Output: + Printed statistical report + per-seed breakdown. + If improvements are significant, a README snippet is printed. +""" +from __future__ import annotations + +import math +import statistics +import sys +from pathlib import Path +import json, datetime + + +# Ensure project root is on sys.path when running as a script +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from stable_baselines3 import PPO + +from train.gym_wrapper import BudgetRouterGymEnv +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import grade_episode +from budget_router.tasks import HARD_MULTI + +MODEL_PATH = "trained_models/ppo_hard_multi_100k.zip" +EVAL_SEEDS = list(range(10)) +HEURISTIC_BASELINE_GRADER = 0.6094 # confirmed from README (dev seeds 0-9) + + +def _grader(history: list[dict]) -> float: + return float(grade_episode(history)["overall_score"]) + + +def _grader_breakdown(history: list[dict]) -> dict: + g = grade_episode(history) + return {k: round(float(v), 4) for k, v in g.items()} + + +def eval_ppo(model: PPO, seeds: list[int]) -> tuple[list[float], list[dict]]: + scores, breakdowns = [], [] + for seed in seeds: + env = BudgetRouterGymEnv(scenario=HARD_MULTI, seed=seed) + inner = env._env + + obs, _ = env.reset() + done = False + while not done: + action_idx, _ = model.predict(obs, deterministic=True) + obs, _, terminated, truncated, _ = env.step(int(action_idx)) + done = terminated or truncated + + bd = _grader_breakdown(inner._internal.history) + scores.append(bd["overall_score"]) + breakdowns.append(bd) + print(f" [PPO] seed={seed:2d} overall={bd['overall_score']:.4f}" + f" adapt={bd['adaptation_score']:.4f}" + f" budget={bd['budget_score']:.4f}" + f" success={bd['success_score']:.4f}") + return scores, breakdowns + + +def eval_heuristic(seeds: list[int]) -> tuple[list[float], list[dict]]: + scores, breakdowns = [], [] + for seed in seeds: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=HARD_MULTI) + while not obs.done: + obs = env.step(heuristic_baseline_policy(obs)) + bd = _grader_breakdown(env._internal.history) + scores.append(bd["overall_score"]) + breakdowns.append(bd) + print(f" [HEU] seed={seed:2d} overall={bd['overall_score']:.4f}" + f" adapt={bd['adaptation_score']:.4f}" + f" budget={bd['budget_score']:.4f}" + f" success={bd['success_score']:.4f}") + return scores, breakdowns + + +def _confidence_interval_95(values: list[float]) -> tuple[float, float]: + """95% CI using t-distribution (small sample).""" + n = len(values) + mean = statistics.mean(values) + if n < 2: + return mean, mean + se = statistics.stdev(values) / math.sqrt(n) + # t-critical ≈ 2.262 for df=9 (n=10), 95% two-tailed + t_crit = 2.262 + margin = t_crit * se + return mean - margin, mean + margin + + +def main() -> None: + if not Path(MODEL_PATH).exists(): + print(f"[eval] Model not found at {MODEL_PATH}. Run train/train_ppo_hard_multi.py first.") + return + + print(f"[eval] Loading {MODEL_PATH}") + model = PPO.load(MODEL_PATH) + + print("\n─── PPO agent (deterministic, Hard_Multi) ───") + ppo_scores, ppo_breakdowns = eval_ppo(model, EVAL_SEEDS) + + print("\n─── Heuristic baseline (Hard_Multi) ───") + heuristic_scores, heuristic_breakdowns = eval_heuristic(EVAL_SEEDS) + + # ── Statistics ────────────────────────────────────────────────────────── + ppo_mean = statistics.mean(ppo_scores) + ppo_std = statistics.stdev(ppo_scores) + heu_mean = statistics.mean(heuristic_scores) + heu_std = statistics.stdev(heuristic_scores) + + delta = ppo_mean - heu_mean + delta_pct = (delta / heu_mean) * 100 if heu_mean > 0 else float("nan") + + ppo_lo, ppo_hi = _confidence_interval_95(ppo_scores) + heu_lo, heu_hi = _confidence_interval_95(heuristic_scores) + + # Win rate: fraction of seeds where PPO > heuristic + win_rate = sum(p > h for p, h in zip(ppo_scores, heuristic_scores)) / len(ppo_scores) + + # Sub-score deltas + avg_adapt_ppo = statistics.mean(b["adaptation_score"] for b in ppo_breakdowns) + avg_adapt_heu = statistics.mean(b["adaptation_score"] for b in heuristic_breakdowns) + avg_budget_ppo = statistics.mean(b["budget_score"] for b in ppo_breakdowns) + avg_budget_heu = statistics.mean(b["budget_score"] for b in heuristic_breakdowns) + + sign = "+" if delta >= 0 else "" + + print(f""" +══════════════════════════════════════════════════════════ + HARD_MULTI: PPO vs Heuristic — Statistical Report +══════════════════════════════════════════════════════════ + + PPO grader: {ppo_mean:.4f} ± {ppo_std:.4f} 95% CI [{ppo_lo:.4f}, {ppo_hi:.4f}] + HEU grader: {heu_mean:.4f} ± {heu_std:.4f} 95% CI [{heu_lo:.4f}, {heu_hi:.4f}] + Delta: {sign}{delta:.4f} ({sign}{delta_pct:.1f}%) + Win rate: {win_rate:.0%} ({int(win_rate*len(ppo_scores))}/{len(ppo_scores)} seeds PPO wins) + + ── Sub-score breakdown ── + Adaptation: PPO={avg_adapt_ppo:.4f} HEU={avg_adapt_heu:.4f} Δ={avg_adapt_ppo-avg_adapt_heu:+.4f} + Budget: PPO={avg_budget_ppo:.4f} HEU={avg_budget_heu:.4f} Δ={avg_budget_ppo-avg_budget_heu:+.4f} +""") + + # ── Verdict ────────────────────────────────────────────────────────────── + if ppo_lo > heu_hi: + verdict = "✅ STRONG: PPO 95% CI is entirely above heuristic 95% CI — non-overlapping." + elif ppo_mean > heu_mean and win_rate >= 0.70: + verdict = f"✅ CLEAR: PPO wins {win_rate:.0%} of seeds with positive mean improvement." + elif ppo_mean > heu_mean and win_rate >= 0.50: + verdict = f"⚠️ MODERATE: PPO wins {win_rate:.0%} of seeds — improvement present but with variance." + elif ppo_mean > heu_mean: + verdict = f"⚠️ WEAK: Mean improvement is positive but PPO wins only {win_rate:.0%} of seeds." + else: + verdict = f"❌ NO IMPROVEMENT: PPO ({ppo_mean:.4f}) ≤ heuristic ({heu_mean:.4f}) — more training needed." + + print(f" VERDICT: {verdict}") + print("══════════════════════════════════════════════════════════\n") + + if ppo_mean > heu_mean: + print(" README snippet (paste into benchmark table):") + print(f" Hard_Multi PPO row: {ppo_mean:.4f} (Δ {sign}{delta_pct:.1f}% vs heuristic)") + + # JSON-serializable summary including per-seed breakdowns + episodes = [] + for seed, p_bd, h_bd in zip(EVAL_SEEDS, ppo_breakdowns, heuristic_breakdowns): + episodes.append({ + "seed": seed, + "ppo": p_bd, + "heuristic": h_bd, + }) + + out = { + "timestamp": datetime.datetime.now().strftime("%Y%m%d_%H%M%S"), + "ppo_mean": ppo_mean, + "ppo_std": ppo_std, + "ppo_ci": [ppo_lo, ppo_hi], + "heu_mean": heu_mean, + "heu_std": heu_std, + "heu_ci": [heu_lo, heu_hi], + "delta": delta, + "delta_pct": delta_pct, + "win_rate": win_rate, + "episodes": episodes, + } + Path("outputs/ppo_hard_multi_eval.json").write_text(json.dumps(out, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/train/eval_ppo.py b/train/eval_ppo.py new file mode 100644 index 0000000000000000000000000000000000000000..1d61927ed8958ec3603112c10e69fd8b50b8e214 --- /dev/null +++ b/train/eval_ppo.py @@ -0,0 +1,97 @@ +""" +Evaluate the trained PPO agent against the heuristic baseline. + +Usage: + uv run python train/eval_ppo.py + +Loads trained_models/ppo_easy_50k.zip and runs 10 episodes (seeds 0-9), +reporting per-episode and mean grader scores. +""" +from __future__ import annotations + +import statistics +from pathlib import Path + +from stable_baselines3 import PPO + +from train.gym_wrapper import BudgetRouterGymEnv +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import grade_episode +from budget_router.tasks import EASY + +MODEL_PATH = "trained_models/ppo_easy_50k.zip" +EVAL_SEEDS = list(range(10)) # seeds 0-9 (development set) +HEURISTIC_BASELINE = 0.7958 # confirmed grader score from README + + +def _grader_score_from_history(history: list[dict]) -> float: + """Compute grader score directly from the environment's history dict.""" + return float(grade_episode(history)["overall_score"]) + + +def eval_ppo(model: PPO, seeds: list[int]) -> list[float]: + """Run PPO policy for each seed, return list of grader scores.""" + scores = [] + for seed in seeds: + env = BudgetRouterGymEnv(scenario=EASY, seed=seed) + inner_env = env._env # direct access to BudgetRouterEnv for history + + obs, _ = env.reset() + done = False + while not done: + action_idx, _ = model.predict(obs, deterministic=True) + obs, _, terminated, truncated, _ = env.step(int(action_idx)) + done = terminated or truncated + + score = _grader_score_from_history(inner_env._internal.history) + scores.append(score) + print(f" seed={seed:2d} grader={score:.4f}") + return scores + + +def eval_heuristic(seeds: list[int]) -> list[float]: + """Run heuristic policy for each seed, return list of grader scores.""" + scores = [] + for seed in seeds: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=EASY) + while not obs.done: + action = heuristic_baseline_policy(obs) + obs = env.step(action) + score = _grader_score_from_history(env._internal.history) + scores.append(score) + return scores + + +def main() -> None: + if not Path(MODEL_PATH).exists(): + print(f"[eval] Model not found at {MODEL_PATH}. Run train/train_ppo.py first.") + return + + print(f"[eval] Loading {MODEL_PATH}") + model = PPO.load(MODEL_PATH) + + print("\n[eval] PPO agent (deterministic):") + ppo_scores = eval_ppo(model, EVAL_SEEDS) + ppo_mean = statistics.mean(ppo_scores) + + print("\n[eval] Heuristic baseline:") + heuristic_scores = eval_heuristic(EVAL_SEEDS) + heuristic_mean = statistics.mean(heuristic_scores) + + print("\n── Results ──────────────────────────────────") + print(f" PPO mean grader score : {ppo_mean:.4f}") + print(f" Heuristic mean grader : {heuristic_mean:.4f} (expected ≈ {HEURISTIC_BASELINE})") + delta = ppo_mean - heuristic_mean + sign = "+" if delta >= 0 else "" + print(f" Delta (PPO - heuristic): {sign}{delta:.4f}") + if ppo_mean > 0.60: + print(" ✅ PPO > 0.60 threshold — README update warranted.") + else: + print(" ⚠️ PPO < 0.60 — keep scaffolding but skip README PPO row.") + + +if __name__ == "__main__": + main() diff --git a/train/eval_trained.py b/train/eval_trained.py new file mode 100644 index 0000000000000000000000000000000000000000..c7f0d22c1c558d67beaf6a4b50f6b36c82d0ea81 --- /dev/null +++ b/train/eval_trained.py @@ -0,0 +1,167 @@ +""" +eval_trained.py — Evaluate the GRPO-trained model against the heuristic baseline. + +Loads the merged model from trained_models/grpo_qwen3_0.6b/ directly (no API server needed). +Runs N episodes on hard_multi and prints mean reward vs heuristic baseline. + +USAGE + uv run python train/eval_trained.py + +HOW IT WORKS + The trained model is loaded as a plain AutoModelForCausalLM (LoRA already merged). + At each step, we feed the current observation as a chat message and parse the + model's text output as a tool call (same _parse_llm_action logic as inference.py). +""" + +from __future__ import annotations + +import argparse +import os +import sys + +os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1") +os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") + +import torch +from transformers import AutoModelForCausalLM, AutoTokenizer + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType, Observation +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import grade_episode +from budget_router.tasks import HARD_MULTI + +N_EPISODES = 10 +SCENARIO = HARD_MULTI + +SYSTEM_PROMPT = ( + "You are a budget-aware API router. " + "Use the available tools to route each request to the best provider. " + "Providers can degrade mid-episode — monitor health and switch early.\n\n" + "At each step output EXACTLY ONE action string from: " + "route_to_a | route_to_b | route_to_c | shed_load" +) + +_VALID_ACTIONS = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] + + +def _parse_action(text: str) -> str: + text = text.strip().lower() + for a in _VALID_ACTIONS: + if a in text: + return a + return "shed_load" + + +def _obs_to_text(obs: Observation) -> str: + return ( + f"provider_a_status: {obs.provider_a_status:.3f}\n" + f"provider_b_status: {obs.provider_b_status:.3f}\n" + f"provider_c_status: {obs.provider_c_status:.3f}\n" + f"budget_remaining: {obs.budget_remaining:.3f}\n" + f"step_count: {obs.step_count:.3f}\n" + f"Your action:" + ) + + +def run_episode_llm(model, tokenizer, seed: int, device: str) -> float: + env = BudgetRouterEnv() + obs = env.reset(scenario=SCENARIO, seed=seed) + messages = [{"role": "system", "content": SYSTEM_PROMPT}] + + while not obs.done: + messages.append({"role": "user", "content": _obs_to_text(obs)}) + try: + text = tokenizer.apply_chat_template( + messages, + tokenize=False, + add_generation_prompt=True, + chat_template_kwargs={"enable_thinking": False}, + ) + except TypeError: + # Older Transformers versions may not expose chat_template_kwargs here. + text = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True + ) + inputs = tokenizer(text, return_tensors="pt").to(device) + with torch.no_grad(): + out = model.generate( + **inputs, + max_new_tokens=20, + do_sample=False, + pad_token_id=tokenizer.eos_token_id, + ) + generated = tokenizer.decode( + out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True + ) + action_str = _parse_action(generated) + messages.append({"role": "assistant", "content": action_str}) + + action = Action(action_type=ActionType(action_str)) + obs = env.step(action) + + return float(grade_episode(env._internal.history)["overall_score"]) + + +def run_episode_heuristic(seed: int) -> float: + env = BudgetRouterEnv() + obs = env.reset(scenario=SCENARIO, seed=seed) + while not obs.done: + action = heuristic_baseline_policy(obs) + obs = env.step(action) + return float(grade_episode(env._internal.history)["overall_score"]) + + +def main(): + parser = argparse.ArgumentParser(description="Evaluate a GRPO-trained model vs heuristic baseline.") + parser.add_argument( + "--model-path", + type=str, + default="trained_models/grpo_Qwen_Qwen3-1.7B", + help="Path to merged trained model directory (default: trained_models/grpo_Qwen_Qwen3-1.7B).", + ) + parser.add_argument("--n-episodes", type=int, default=N_EPISODES, help="Number of eval episodes.") + args = parser.parse_args() + + model_path = args.model_path + if not os.path.exists(model_path): + print(f"❌ Trained model not found at {MODEL_PATH}") + print(" Run train/learn_experiment.py first.") + sys.exit(1) + + device = "mps" if torch.backends.mps.is_available() else "cpu" + dtype = torch.bfloat16 if device == "mps" else torch.float32 + + print(f"Loading trained model from {model_path} ...") + model = AutoModelForCausalLM.from_pretrained(model_path, dtype=dtype) + model = model.to(device) + model.eval() + tokenizer = AutoTokenizer.from_pretrained(model_path) + + print(f"\nRunning {args.n_episodes} episodes on {SCENARIO.name} ...") + print(f"{'Seed':<6} {'LLM':>8} {'Heuristic':>12}") + print("-" * 30) + + llm_scores, heuristic_scores = [], [] + for seed in range(args.n_episodes): + llm_r = run_episode_llm(model, tokenizer, seed, device) + heur_r = run_episode_heuristic(seed) + llm_scores.append(llm_r) + heuristic_scores.append(heur_r) + print(f"{seed:<6} {llm_r:>8.4f} {heur_r:>12.4f}") + + llm_mean = sum(llm_scores) / len(llm_scores) + heur_mean = sum(heuristic_scores) / len(heuristic_scores) + + print("-" * 30) + print(f"{'Mean':<6} {llm_mean:>8.4f} {heur_mean:>12.4f}") + print() + if llm_mean >= heur_mean: + print(f"✅ LLM ({llm_mean:.4f}) >= Heuristic ({heur_mean:.4f}) — BEATS BASELINE") + else: + gap = heur_mean - llm_mean + print(f"⚠️ LLM ({llm_mean:.4f}) < Heuristic ({heur_mean:.4f}) — gap={gap:.4f}") + + +if __name__ == "__main__": + main() diff --git a/train/gen_outputs.py b/train/gen_outputs.py new file mode 100644 index 0000000000000000000000000000000000000000..d4ff3e1e01a53c6309c7a9dba0149c89e51ae191 --- /dev/null +++ b/train/gen_outputs.py @@ -0,0 +1,96 @@ +""" +Generate sample episode output JSON files for easy (seed 42) and hard_multi (seed 42). + +Usage: + uv run python train/gen_outputs.py + +Output: + outputs/episode_easy_seed42.json + outputs/episode_hard_multi_seed42.json +""" +from __future__ import annotations + +import json +from pathlib import Path + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action +from budget_router.policies import heuristic_baseline_policy +from budget_router.reward import grade_episode +from budget_router.tasks import EASY, HARD_MULTI + + +def capture_episode(scenario, seed: int) -> dict: + env = BudgetRouterEnv() + obs = env.reset(seed=seed, scenario=scenario) + steps = [] + + while not obs.done: + action: Action = heuristic_baseline_policy(obs) + obs_before = { + "provider_a_status": float(obs.provider_a_status), + "provider_b_status": float(obs.provider_b_status), + "provider_c_status": float(obs.provider_c_status), + "budget_remaining": float(obs.budget_remaining), + "queue_backlog": float(obs.queue_backlog), + "system_latency": float(obs.system_latency), + "step_count": float(obs.step_count), + } + obs = env.step(action) + # Serialize metadata — coerce non-JSON-native types + meta = {} + for k, v in (obs.metadata or {}).items(): + if isinstance(v, (int, float, bool, str, type(None))): + meta[k] = v + else: + meta[k] = str(v) + + steps.append({ + "step": int(meta.get("step", len(steps) + 1)), + "action": action.action_type.value, + "observation_before": obs_before, + "observation_after": { + "provider_a_status": float(obs.provider_a_status), + "provider_b_status": float(obs.provider_b_status), + "provider_c_status": float(obs.provider_c_status), + "budget_remaining": float(obs.budget_remaining), + "queue_backlog": float(obs.queue_backlog), + "system_latency": float(obs.system_latency), + "step_count": float(obs.step_count), + }, + "reward": float(obs.reward), + "done": bool(obs.done), + "metadata": meta, + }) + + grader_raw = grade_episode(env._internal.history) + grader = {k: float(v) for k, v in grader_raw.items()} + return { + "scenario": scenario.name, + "seed": seed, + "policy": "heuristic", + "total_steps": len(steps), + "grader": grader, + "steps": steps, + } + + +def main() -> None: + output_dir = Path("outputs") + output_dir.mkdir(exist_ok=True) + + print("Capturing easy seed=42 ...") + easy = capture_episode(EASY, 42) + (output_dir / "episode_easy_seed42.json").write_text(json.dumps(easy, indent=2)) + print(f" total_steps={easy['total_steps']} grader={easy['grader']['overall_score']:.4f}") + + print("Capturing hard_multi seed=42 ...") + hm = capture_episode(HARD_MULTI, 42) + (output_dir / "episode_hard_multi_seed42.json").write_text(json.dumps(hm, indent=2)) + print(f" total_steps={hm['total_steps']} grader={hm['grader']['overall_score']:.4f}") + + print("✅ Done — outputs/ contains 2 valid JSON files") + + +if __name__ == "__main__": + main() diff --git a/train/grpo_env.py b/train/grpo_env.py new file mode 100644 index 0000000000000000000000000000000000000000..362b5fa3b82cd7f93f9c572440b8c7f3941bd5de --- /dev/null +++ b/train/grpo_env.py @@ -0,0 +1,274 @@ +""" +BudgetRouterGRPOEnv — TRL environment_factory-compatible class for GRPO training. + +Usage with TRL GRPOTrainer: + from datasets import Dataset + from train.grpo_env import BudgetRouterGRPOEnv + from budget_router.reward import grade_episode + + # Dataset: columns become **kwargs in reset(). "prompt" drives the model's initial message. + dataset = Dataset.from_list([ + {"prompt": [[{"role": "user", "content": "Route requests using the available tools."}]], + "scenario": "hard_multi", "seed": i} + for i in range(200) + ]) + + # reward_funcs with an `environments` parameter is the CORRECT TRL pattern when using + # environment_factory. TRL inspects the signature and passes env instances (not completions). + # This is explicitly documented in the official TRL/OpenEnv integration guide. + # Alternatively, env.reward is set on the instance and TRL reads it directly if + # reward_funcs is omitted — but the explicit function gives more control. + def reward_func(environments, **kwargs): + return [float(grade_episode(env._env._internal.history)["overall_score"]) + for env in environments] + + trainer = GRPOTrainer( + model=model, + reward_funcs=reward_func, + train_dataset=dataset, + args=GRPOConfig(num_generations=8, max_completion_length=2048), + environment_factory=BudgetRouterGRPOEnv, + ) + +Design Constraints (do NOT violate): + - Tool methods MUST call self._env.step() and never construct custom step_info dicts. + environment.py writes actual_degradation_start (jittered per-episode) into step_info. + grade_episode() reads degradation_start_step from that dict to compute adaptation windows. + Custom dicts would write the config constant (e.g. 0) instead of the jittered value, + silently corrupting adaptation scores with no crash. + - History is authoritative at self._env._internal.history — never maintain a separate copy. + - Reward is computed once at episode end via grade_episode()["overall_score"] (float in [0,1]). + - Raise ValueError("Episode complete.") when done — TRL catches this and ends the rollout. + +Mac / MPS Notes: + - Unsloth does NOT support Mac for training (CUDA-only as of Apr 2026). + - Use TRL + PyTorch MPS: no load_in_4bit, no vLLM, no paged_adamw_8bit. + - PYTORCH_ENABLE_MPS_FALLBACK=1 required for ops not yet on Metal. + - Recommended models for Mac: Qwen2.5-1.5B (fits 8GB+), Qwen2.5-3B (fits 16GB+). + - For Colab/cloud: Unsloth + vLLM work normally on NVIDIA T4/A100. + +Reward Variance Note: + - GRPO gradient = 0 when group_std ≈ 0. Use hard_multi scenario (not easy). + - hard_multi has jitter + dual degradation → wider inter-rollout score spread. + - num_generations=8 (not 4) recommended to get better group variance estimates. +""" + +from __future__ import annotations + +from typing import Optional + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.reward import grade_episode +from budget_router.tasks import HARD_MULTI, TASK_PRESETS + + +class BudgetRouterGRPOEnv: + """ + TRL environment_factory-compatible wrapper around BudgetRouterEnv. + + Exposes four named tool methods: route_to_a, route_to_b, route_to_c, shed_load. + The LLM calls these via function-calling. TRL discovers them automatically. + + Episode lifecycle: + 1. reset(**kwargs) → returns rich text observation (initial state). + 2. Model calls tool methods N times until episode ends. + 3. Tool method raises ValueError("Episode complete.") when obs.done is True. + 4. TRL reads self.reward from the reward_func after the episode. + """ + + def __init__(self) -> None: + self._env = BudgetRouterEnv() + self.reward: float = 0.0 + + # ─── TRL lifecycle ────────────────────────────────────────────────── + + def reset(self, **kwargs) -> str: + """ + Reset the environment. Called by TRL at the start of each episode. + + Accepts dataset columns as kwargs: + scenario (str): one of "easy", "medium", "hard", "hard_multi" (default). + seed (int): optional fixed seed for reproducibility. + + Returns: + str: Initial observation text including provider status, budget, and task brief. + """ + scenario_name = str(kwargs.get("scenario", "hard_multi")) + scenario = TASK_PRESETS.get(scenario_name, HARD_MULTI) + seed: Optional[int] = kwargs.get("seed", None) + if seed is not None: + seed = int(seed) + + self._env.reset(seed=seed, scenario=scenario) + self.reward = 0.0 + + return self._format_observation(is_initial=True) + + # ─── Tool methods (TRL discovers all public non-reset methods) ─────── + + def route_to_a(self) -> str: + """ + Route the current request to Provider A ($0.01/req, cheapest, lowest base reliability). + + Args: + (none) + + Returns: + Outcome feedback: success/failure, latency, budget remaining, provider health update. + """ + return self._step(ActionType.ROUTE_TO_A) + + def route_to_b(self) -> str: + """ + Route the current request to Provider B ($0.05/req, balanced cost and reliability). + + Args: + (none) + + Returns: + Outcome feedback: success/failure, latency, budget remaining, provider health update. + """ + return self._step(ActionType.ROUTE_TO_B) + + def route_to_c(self) -> str: + """ + Route the current request to Provider C ($0.10/req, most expensive, highest base reliability). + + Args: + (none) + + Returns: + Outcome feedback: success/failure, latency, budget remaining, provider health update. + """ + return self._step(ActionType.ROUTE_TO_C) + + def shed_load(self) -> str: + """ + Shed the current request — reject it without routing to any provider. + Use when all providers appear degraded or budget is critically low. + Penalty: -0.5 step reward. Slightly reduces queue backlog. + + Args: + (none) + + Returns: + Outcome feedback: load shed confirmation, budget remaining, current state. + """ + return self._step(ActionType.SHED_LOAD) + + # ─── Internal step dispatch ────────────────────────────────────────── + + def _step(self, action_type: ActionType) -> str: + """ + Execute one environment step. + + CRITICAL: Delegates entirely to self._env.step(). Never constructs a custom + step_info dict. environment.py writes actual_degradation_start (the jittered + per-episode onset) into step_info; grade_episode() reads this to compute + adaptation scores. A custom dict would use the config constant instead, + silently breaking adaptation scoring. + """ + if self._env._internal.episode_done: + # Guard: called after done — reuse last reward, signal TRL to stop + raise ValueError( + f"Episode already complete. Final score: {self.reward:.3f}" + ) + + action = Action(action_type=action_type) + obs = self._env.step(action) # step_info written to self._env._internal.history + + # Format response text BEFORE checking done (obs fields still valid) + response = self._format_step_result(obs) + + if obs.done: + # History is authoritative at self._env._internal.history + self.reward = float( + grade_episode(self._env._internal.history)["overall_score"] + ) + raise ValueError( + f"Episode complete. Score: {self.reward:.3f}. {response}" + ) + + return response + + # ─── Observation / response formatters ────────────────────────────── + + def _format_observation(self, is_initial: bool = False) -> str: + """Format current env state as a rich text observation string.""" + obs = self._env._get_obs() + s = self._env._internal + config = self._env._config + + steps_remaining = max(0, s.max_steps - s.current_step) + budget_dollars = s.budget_dollars + budget_pct = obs.budget_remaining * 100.0 + + lines = [] + if is_initial: + lines.append( + f"=== Budget Router — {config.name.upper()} ===\n" + f"Budget: ${budget_dollars:.3f} ({budget_pct:.1f}% remaining) | " + f"Steps remaining: {steps_remaining}/{s.max_steps}\n" + f"Providers: A=$0.01/req (cheapest), B=$0.05/req, C=$0.10/req (most reliable)\n" + f"Goal: Maximize successful routed requests. Budget exhaustion = heavy penalty.\n" + ) + else: + lines.append( + f"Budget: ${budget_dollars:.3f} ({budget_pct:.1f}%) | " + f"Steps remaining: {steps_remaining}" + ) + + lines.append( + f"Provider health (windowed success rate; 0.5 = unobserved):\n" + f" A: {obs.provider_a_status:.3f} | B: {obs.provider_b_status:.3f} | C: {obs.provider_c_status:.3f}\n" + f"Queue backlog: {obs.queue_backlog:.3f} (normalized) | " + f"System latency: {obs.system_latency:.3f} (normalized to SLA)\n" + ) + + if is_initial: + lines.append( + "Choose a routing action: route_to_a / route_to_b / route_to_c / shed_load" + ) + + return "\n".join(lines) + + def _format_step_result(self, obs) -> str: + """Format step outcome as text returned to the model.""" + s = self._env._internal + history = s.history + if not history: + return self._format_observation() + + last = history[-1] + action_type = last.get("action_type", "unknown") + succeeded = last.get("request_succeeded", False) + provider = last.get("provider") + latency = last.get("latency_ms", 0.0) + cost = last.get("cost", 0.0) + budget_exhausted = last.get("budget_exhausted", False) + queue_overflow = last.get("queue_overflow", False) + + if action_type == "shed_load": + result = "shed" + elif budget_exhausted: + result = "budget_exhausted" + elif succeeded: + result = "ok" + else: + result = "fail" + + overflow_note = " overflow=1" if queue_overflow else "" + step_num = last.get("step", s.current_step) + + obs_obj = self._env._get_obs() + budget_pct = obs_obj.budget_remaining * 100.0 + steps_remaining = max(0, s.max_steps - s.current_step) + + return ( + f"step={step_num} action={action_type} result={result} p={provider or '-'} " + f"lat={latency:.0f} cost={cost:.3f} budget={budget_pct:.1f}% " + f"steps_left={steps_remaining} health=A{obs_obj.provider_a_status:.2f}/" + f"B{obs_obj.provider_b_status:.2f}/C{obs_obj.provider_c_status:.2f} " + f"queue={obs_obj.queue_backlog:.2f}{overflow_note}" + ) diff --git a/train/gym_wrapper.py b/train/gym_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..b3f6b872abfbaf482e65d5369cb5f37134509b2f --- /dev/null +++ b/train/gym_wrapper.py @@ -0,0 +1,119 @@ +""" +Gymnasium wrapper for BudgetRouterEnv. + +Wraps a single scenario (default: Easy) into a standard Gymnasium interface +compatible with stable-baselines3 and other RL libraries. + +Observation space: Box(7,) float32 — all fields normalized to [0, 1] + [provider_a_status, provider_b_status, provider_c_status, + budget_remaining, queue_backlog, system_latency, step_count] + +Action space: Discrete(4) + 0 → route_to_a + 1 → route_to_b + 2 → route_to_c + 3 → shed_load +""" +from __future__ import annotations + +import numpy as np +import gymnasium as gym +from gymnasium import spaces + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.tasks import EASY, TaskConfig + + +_ACTION_MAP = [ + ActionType.ROUTE_TO_A, + ActionType.ROUTE_TO_B, + ActionType.ROUTE_TO_C, + ActionType.SHED_LOAD, +] + + +class BudgetRouterGymEnv(gym.Env): + """Gymnasium-compatible wrapper around BudgetRouterEnv. + + Args: + scenario: TaskConfig to use. Defaults to EASY. + seed: Fixed seed for reproducible resets. If None, a random seed is + drawn from the Gymnasium RNG on each reset(). + """ + + metadata = {"render_modes": []} + + def __init__(self, scenario: TaskConfig = EASY, seed: int | None = None) -> None: + super().__init__() + self._env = BudgetRouterEnv() + self._scenario = scenario + self._fixed_seed = seed + self._episode_seed: int | None = seed + + # 7-dimensional normalized observation + self.observation_space = spaces.Box( + low=0.0, + high=1.0, + shape=(7,), + dtype=np.float32, + ) + + # 4 discrete actions: route_to_a, route_to_b, route_to_c, shed_load + self.action_space = spaces.Discrete(4) + + # ------------------------------------------------------------------ + # Gymnasium API + # ------------------------------------------------------------------ + + def reset( + self, + *, + seed: int | None = None, + options: dict | None = None, + ) -> tuple[np.ndarray, dict]: + super().reset(seed=seed) + + # Use fixed seed if provided at construction, otherwise use Gymnasium's RNG + if self._fixed_seed is not None: + episode_seed = self._fixed_seed + elif seed is not None: + episode_seed = seed + else: + episode_seed = int(self.np_random.integers(0, 2**31 - 1)) + + self._episode_seed = episode_seed + obs = self._env.reset(seed=episode_seed, scenario=self._scenario) + return self._obs_to_array(obs), {} + + def step(self, action: int) -> tuple[np.ndarray, float, bool, bool, dict]: + action_type = _ACTION_MAP[int(action)] + obs = self._env.step(Action(action_type=action_type)) + reward = float(obs.reward or 0.0) + terminated = bool(obs.done) + truncated = False # termination handled by env's max_steps + info = dict(obs.metadata or {}) + return self._obs_to_array(obs), reward, terminated, truncated, info + + def render(self) -> None: + pass # no rendering required + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + @staticmethod + def _obs_to_array(obs) -> np.ndarray: + """Convert Observation dataclass to a (7,) float32 numpy array.""" + return np.array( + [ + obs.provider_a_status, + obs.provider_b_status, + obs.provider_c_status, + obs.budget_remaining, + obs.queue_backlog, + obs.system_latency, + obs.step_count, + ], + dtype=np.float32, + ) diff --git a/train/learn_experiment.py b/train/learn_experiment.py new file mode 100644 index 0000000000000000000000000000000000000000..3e93bc61ebce47be1f94bf5d588c4f0b6ad96f29 --- /dev/null +++ b/train/learn_experiment.py @@ -0,0 +1,491 @@ +""" +GRPO Learning Experiment — Budget Router (~90 min on M4 Mac MPS) + +GOAL + Determine (80-90% confidence) whether BudgetRouterGRPOEnv provides a + learnable signal for GRPO on Qwen/Qwen3-0.6B. + +TIMING (from smoke test) + Outer iteration = 1 rollout step (~50s) + 3 gradient steps (~6s) = ~56s + max_steps=360 → ~90 outer iterations → ~84 min on M4 48GB MPS + +VERDICT LOGIC + Compare mean reward of first 25% vs last 25% of rollout steps. + - LEARNING DETECTED : last_quarter > first_quarter + 0.05 + - NOT LEARNING : last_quarter < first_quarter - 0.02 + - INCONCLUSIVE : otherwise (high variance, too few tool calls) + +USAGE + PYTORCH_ENABLE_MPS_FALLBACK=1 uv run python train/learn_experiment.py + +KEY DIFFERENCES FROM smoke_test.py + - max_steps=360 (vs 10) + - learning_rate=5e-6 (vs 5e-7 — standard GRPO lr per DeepSeek-R1 paper) + - Proper rollout-only callback: separates data-collection from gradient steps + - Rolling reward average printed every 10 rollout steps + - VERDICT analysis at the end +""" + +from __future__ import annotations + +import argparse +import math +import os +import sys +import time + +os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1") +os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") + +import torch +from collections import Counter + +from datasets import Dataset +from peft import LoraConfig +from transformers import AutoModelForCausalLM, AutoTokenizer, TrainerCallback +from trl import GRPOConfig, GRPOTrainer + +from budget_router.reward import grade_episode +from train.grpo_env import BudgetRouterGRPOEnv + +# ── Config ─────────────────────────────────────────────────────────────────── + +# NOTE: model is configurable via --model-name (defaults to a stronger tool caller). +DEFAULT_MODEL_NAME = "Qwen/Qwen3-1.7B" +PROMPT_STYLES = ("strict", "explore") + +STRICT_SYSTEM_PROMPT = ( + "You are a budget-aware API router. " + "Use the available tools to route each request to the best provider. " + "Providers can degrade mid-episode — monitor health and switch early.\n\n" + "TOOL CALL FORMAT — you must use this EXACT format to call a tool:\n" + "\n{\"name\": \"route_to_a\", \"arguments\": {}}\n\n\n" + "EXAMPLES:\n" + " Observation: A=0.833, B=0.500, C=0.500, budget=95%\n" + " Action: \n{\"name\": \"route_to_a\", \"arguments\": {}}\n\n\n" + " Observation: A=0.100 (degraded!), B=0.833, C=0.500, budget=70%\n" + " Action: \n{\"name\": \"route_to_b\", \"arguments\": {}}\n\n\n" + "Route to the cheapest healthy provider (A=$0.01 < B=$0.05 < C=$0.10). " + "Switch early if a provider's health drops below 0.52." +) + +EXPLORE_SYSTEM_PROMPT = ( + "You are a budget-aware API router. " + "Use exactly one available tool after each observation to route the current request.\n\n" + "TOOL CALL FORMAT — respond with only this XML-wrapped JSON shape:\n" + "\n{\"name\": \"route_to_a\", \"arguments\": {}}\n\n\n" + "Available tools: route_to_a, route_to_b, route_to_c, shed_load.\n" + "Objective: maximize successful routed requests while preserving budget and avoiding " + "unhealthy or overloaded providers. Providers can degrade mid-episode, so use the " + "latest observed health, latency, queue, budget, and steps-left information.\n" + "Do not follow a fixed provider cycle. Change actions only when the observations " + "make a different provider or shedding load look better." +) + + +def build_system_prompt(prompt_style: str = "strict") -> str: + if prompt_style == "strict": + return STRICT_SYSTEM_PROMPT + if prompt_style == "explore": + return EXPLORE_SYSTEM_PROMPT + raise ValueError(f"Unknown prompt_style={prompt_style!r}; expected one of {PROMPT_STYLES}") + +# ── Reward function ────────────────────────────────────────────────────────── + +LAST_ROLLOUT_DIAGNOSTICS: dict[str, object] = {} + + +def episode_training_reward(env: BudgetRouterGRPOEnv) -> float: + internal = env._env._internal + history = internal.history + if not history: + return 0.0 + + grader = float(grade_episode(history)["overall_score"]) + if internal.episode_done: + return grader + + progress = internal.current_step / max(1, internal.max_steps) + return grader * progress + + +def _mean(values: list[float]) -> float: + return sum(values) / len(values) if values else 0.0 + + +def _action_sequence(history) -> str: + actions = [str(step.get("action_type", "unknown")) for step in history] + return " ".join(actions) if actions else "" + + +def summarize_training_rollout(environments) -> dict[str, object]: + env_steps = [] + progress = [] + raw_graders = [] + training_rewards = [] + completions = [] + budget_exhaustions = [] + action_sequences = [] + + for env in environments: + internal = env._env._internal + history = internal.history + env_steps.append(float(internal.current_step)) + progress.append(float(internal.current_step / max(1, internal.max_steps))) + raw_graders.append(float(grade_episode(history)["overall_score"]) if history else 0.0) + training_rewards.append(episode_training_reward(env)) + completions.append(1.0 if internal.episode_done else 0.0) + budget_exhaustions.append( + 1.0 if any(step.get("budget_exhausted", False) for step in history) else 0.0 + ) + action_sequences.append(_action_sequence(history)) + + sequence_counts = dict(Counter(action_sequences)) + + return { + "env_steps_mean": _mean(env_steps), + "env_steps_min": min(env_steps) if env_steps else 0.0, + "env_steps_max": max(env_steps) if env_steps else 0.0, + "progress_mean": _mean(progress), + "raw_grader_mean": _mean(raw_graders), + "training_reward_mean": _mean(training_rewards), + "training_rewards": training_rewards, + "episode_completion_rate": _mean(completions), + "budget_exhaustion_rate": _mean(budget_exhaustions), + "action_sequences": action_sequences, + "unique_action_sequences": len(sequence_counts), + "action_sequence_counts": sequence_counts, + } + + +def reward_func(environments, **kwargs): + global LAST_ROLLOUT_DIAGNOSTICS + LAST_ROLLOUT_DIAGNOSTICS = summarize_training_rollout(environments) + return [episode_training_reward(env) for env in environments] + +# ── Dataset ────────────────────────────────────────────────────────────────── + +def build_dataset(n: int = 200, prompt_style: str = "strict") -> Dataset: + system_prompt = build_system_prompt(prompt_style) + return Dataset.from_list([ + { + "prompt": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": "Route the incoming requests optimally."}, + ], + "scenario": "hard_multi", + "seed": i, + } + for i in range(n) + ]) + +# ── Callback ───────────────────────────────────────────────────────────────── + +class LearnCallback(TrainerCallback): + """ + Tracks rollout steps (data-collection) separately from gradient-only steps. + GRPO pattern: 1 rollout step + 3 gradient-only steps = 1 outer iteration. + Only rollout steps carry reward/tool_call metrics. + """ + + ROLLOUT_PRINT_EVERY = 10 # print rolling average every N rollout steps + + def __init__(self): + self.rollout_rewards: list[float] = [] + self.total_grad_steps: int = 0 + self.tool_call_freqs: list[float] = [] + self.env_step_means: list[float] = [] + self.completion_rates: list[float] = [] + + def on_log(self, args, state, control, logs=None, **kwargs): + if not logs or state.global_step == 0: + return + if "train_runtime" in logs: + return + + step = state.global_step + self.total_grad_steps = max(self.total_grad_steps, step) + loss = float(logs.get("loss", float("nan"))) + + # Gradient-only steps have no reward key + if "reward" not in logs: + print(f" [{step:03d}] grad-only | loss={loss:.4f}") + return + + reward = float(logs.get("reward", float("nan"))) + reward_std = float(logs.get("reward_std", float("nan"))) + tool_freq = float(logs.get("tools/call_frequency", float("nan"))) + diagnostics = LAST_ROLLOUT_DIAGNOSTICS + + if not math.isnan(reward): + self.rollout_rewards.append(reward) + if not math.isnan(tool_freq): + self.tool_call_freqs.append(tool_freq) + if diagnostics: + self.env_step_means.append(float(diagnostics["env_steps_mean"])) + self.completion_rates.append(float(diagnostics["episode_completion_rate"])) + + rollout_n = len(self.rollout_rewards) + rolling_avg = ( + sum(self.rollout_rewards[-10:]) / len(self.rollout_rewards[-10:]) + if self.rollout_rewards else float("nan") + ) + sequence_counts = diagnostics.get("action_sequence_counts", {}) if diagnostics else {} + unique_sequences = int(diagnostics.get("unique_action_sequences", 0)) if diagnostics else 0 + group_size = len(diagnostics.get("action_sequences", [])) if diagnostics else 0 + reward_values = diagnostics.get("training_rewards", []) if diagnostics else [] + reward_preview = ",".join(f"{float(v):.4f}" for v in reward_values) + + print( + f" [{step:03d}] ROLLOUT #{rollout_n:02d} | " + f"reward={reward:.4f} | std={reward_std:.4f} | " + f"tool_freq={tool_freq:.2f} | " + f"env_steps={diagnostics.get('env_steps_mean', float('nan')):.1f} | " + f"complete={diagnostics.get('episode_completion_rate', float('nan')):.2f} | " + f"raw={diagnostics.get('raw_grader_mean', float('nan')):.4f} | " + f"seqs={unique_sequences}/{group_size} | " + f"rewards=[{reward_preview}] | " + f"rolling10={rolling_avg:.4f} | loss={loss:.4f}" + ) + if unique_sequences <= 3 and sequence_counts: + counts = " || ".join( + f"{count}x {sequence}" for sequence, count in sequence_counts.items() + ) + print(f" action_sequences: {counts}") + + if rollout_n % self.ROLLOUT_PRINT_EVERY == 0: + self._print_trend_summary() + + def _print_trend_summary(self): + n = len(self.rollout_rewards) + if n < 4: + return + q = max(1, n // 4) + first_q = sum(self.rollout_rewards[:q]) / q + last_q = sum(self.rollout_rewards[-q:]) / q + avg_tool = ( + sum(self.tool_call_freqs) / len(self.tool_call_freqs) + if self.tool_call_freqs else float("nan") + ) + avg_env_steps = ( + sum(self.env_step_means) / len(self.env_step_means) + if self.env_step_means else float("nan") + ) + avg_completion = ( + sum(self.completion_rates) / len(self.completion_rates) + if self.completion_rates else float("nan") + ) + print( + f"\n ── Trend @ rollout {n} ──\n" + f" first-quarter mean : {first_q:.4f}\n" + f" last-quarter mean : {last_q:.4f}\n" + f" delta : {last_q - first_q:+.4f}\n" + f" avg tool_call_freq : {avg_tool:.3f}\n" + f" avg env_steps : {avg_env_steps:.2f}\n" + f" avg completion_rate : {avg_completion:.3f}\n" + ) + +# ── Main ───────────────────────────────────────────────────────────────────── + +def main(): + t0 = time.time() + + parser = argparse.ArgumentParser(description="GRPO Learning Experiment — Budget Router") + parser.add_argument( + "--model-name", + type=str, + default=DEFAULT_MODEL_NAME, + help="HF model id to train (default: Qwen/Qwen3-1.7B).", + ) + parser.add_argument("--max-steps", type=int, default=360, help="Total GRPO max_steps (outer iterations).") + parser.add_argument("--dataset-n", type=int, default=200, help="Number of episodes in training dataset.") + parser.add_argument("--save-steps", type=int, default=60, help="Checkpoint save frequency in steps.") + parser.add_argument("--num-generations", type=int, default=4, help="GRPO generations per prompt.") + parser.add_argument("--max-completion-length", type=int, default=1024, help="Completion token budget across tool loop.") + parser.add_argument("--max-tool-calling-iterations", type=int, default=20, help="Maximum tool loop iterations per rollout.") + parser.add_argument( + "--prompt-style", + choices=PROMPT_STYLES, + default="strict", + help="Prompt style: strict preserves the original heuristic prompt; explore reduces deterministic policy bias.", + ) + parser.add_argument("--temperature", type=float, default=1.0, help="Sampling temperature for GRPO rollouts.") + parser.add_argument("--top-p", type=float, default=1.0, help="Nucleus sampling top-p for GRPO rollouts.") + cli = parser.parse_args() + + if torch.backends.mps.is_available(): + device = "mps" + elif torch.cuda.is_available(): + device = "cuda" + else: + device = "cpu" + + print("=" * 68) + print("GRPO Learning Experiment — Budget Router") + print("=" * 68) + print(f"Device : {device.upper()}") + print(f"Model : {cli.model_name}") + print(f"Steps : {cli.max_steps}") + print(f"Prompt : {cli.prompt_style}") + print(f"Sampling : temperature={cli.temperature} top_p={cli.top_p}") + print(f"Torch : {torch.__version__}") + print("=" * 68) + + print("\nLoading model...") + dtype = torch.bfloat16 if device in ("mps", "cuda") else torch.float32 + model = AutoModelForCausalLM.from_pretrained( + cli.model_name, dtype=dtype, trust_remote_code=True + ) + tokenizer = AutoTokenizer.from_pretrained(cli.model_name, trust_remote_code=True) + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + peft_config = LoraConfig( + r=8, + lora_alpha=16, + target_modules=["q_proj", "v_proj"], + lora_dropout=0.05, + bias="none", + task_type="CAUSAL_LM", + ) + + # Hyperparams: TRL/OpenEnv Wordle example + DeepSeek-R1 paper + # lr=1e-6: standard GRPO (smoke test used 5e-7, too conservative for 360 steps) + # enable_thinking=False: reclaims ~400 tokens/step from Qwen3 reasoning blocks, + # allowing max_completion_length to drop from 512→256 without clipping valid tool calls. + args = GRPOConfig( + max_steps=cli.max_steps, + per_device_train_batch_size=1, + gradient_accumulation_steps=1, + num_generations=cli.num_generations, + generation_batch_size=cli.num_generations, + max_completion_length=cli.max_completion_length, + max_tool_calling_iterations=cli.max_tool_calling_iterations, + temperature=cli.temperature, + top_p=cli.top_p, + beta=0.001, + learning_rate=5e-6, + optim="adamw_torch", + report_to="none", + logging_steps=1, + remove_unused_columns=False, + dataloader_num_workers=0, + save_steps=cli.save_steps, + output_dir="trained_models/grpo_checkpoints", + chat_template_kwargs={"enable_thinking": False}, # Qwen3: skip blocks + ) + + dataset = build_dataset(n=cli.dataset_n, prompt_style=cli.prompt_style) + cb = LearnCallback() + + trainer = GRPOTrainer( + model=model, + processing_class=tokenizer, + reward_funcs=reward_func, + train_dataset=dataset, + args=args, + peft_config=peft_config, + environment_factory=BudgetRouterGRPOEnv, + callbacks=[cb], + ) + + def _save_merged(label: str) -> None: + """ + Merge LoRA weights into the base model and save to disk. + The merged model is a plain HuggingFace model — loadable with + AutoModelForCausalLM.from_pretrained() without any PEFT dependency. + """ + safe_name = ( + cli.model_name.replace("/", "_") + .replace(":", "_") + .replace("@", "_") + ) + save_path = f"trained_models/grpo_{safe_name}" + print(f"\n[{label}] Merging LoRA into base model and saving to {save_path} ...") + try: + merged = trainer.model.merge_and_unload() + merged.save_pretrained(save_path) + tokenizer.save_pretrained(save_path) + print(f"[{label}] ✅ Saved. Load with: AutoModelForCausalLM.from_pretrained('{save_path}')") + except Exception as e: + print(f"[{label}] ⚠️ Save failed: {e}") + + print("\nStarting experiment (Ctrl+C to stop early — partial results still printed)...\n") + try: + trainer.train() + _save_merged("END") + except KeyboardInterrupt: + print("\n[Interrupted by user — computing verdict on partial results...]") + _save_merged("INTERRUPT") + except Exception as exc: + print(f"\n❌ Training error: {type(exc).__name__}: {exc}") + sys.exit(1) + + elapsed = time.time() - t0 + + # ── VERDICT ────────────────────────────────────────────────────────────── + rewards = cb.rollout_rewards + tool_freqs = cb.tool_call_freqs + env_step_means = cb.env_step_means + completion_rates = cb.completion_rates + + print("\n" + "=" * 68) + print("LEARNING EXPERIMENT — VERDICT") + print("=" * 68) + print(f"Grad steps completed : {cb.total_grad_steps}/{cli.max_steps}") + print(f"Rollout steps : {len(rewards)}") + print(f"Elapsed : {elapsed/60:.1f} min") + + if len(rewards) < 4: + print("\n⚠️ Too few rollout steps for a verdict. Run longer.") + sys.exit(0) + + q = max(1, len(rewards) // 4) + first_q_mean = sum(rewards[:q]) / q + last_q_mean = sum(rewards[-q:]) / q + delta = last_q_mean - first_q_mean + avg_tool_freq = sum(tool_freqs) / len(tool_freqs) if tool_freqs else 0.0 + avg_env_steps = sum(env_step_means) / len(env_step_means) if env_step_means else 0.0 + avg_completion_rate = sum(completion_rates) / len(completion_rates) if completion_rates else 0.0 + overall_mean = sum(rewards) / len(rewards) + + print(f"\nReward summary:") + print(f" First-quarter mean : {first_q_mean:.4f}") + print(f" Last-quarter mean : {last_q_mean:.4f}") + print(f" Delta (improvement): {delta:+.4f}") + print(f" Overall mean : {overall_mean:.4f}") + print(f" Avg tool_call_freq : {avg_tool_freq:.3f}") + print(f" Avg env_steps : {avg_env_steps:.2f}") + print(f" Avg completion_rate: {avg_completion_rate:.3f}") + + print(f"\nHeuristic baseline : ~0.60-0.65 (from environment benchmark)") + print(f"Random agent : ~0.15-0.20") + + print() + if delta > 0.05: + print("✅ VERDICT: LEARNING SIGNAL DETECTED") + print(" Reward improved meaningfully across the run.") + print(" Recommend: scale up to Qwen2.5-1.5B-Instruct + num_generations=8.") + elif delta < -0.02: + print("❌ VERDICT: NOT LEARNING") + print(" Reward trended downward. Possible causes:") + print(" - lr too high (try 5e-7)") + print(" - too few tool calls (model not generating tool syntax reliably)") + print(" - environment reward too sparse") + else: + print("⚠️ VERDICT: INCONCLUSIVE") + print(f" Delta={delta:+.4f} is within noise range.") + if avg_tool_freq < 0.3: + print(" Root cause likely: tool_call_freq too low — model rarely uses tools.") + print(" Fix: add few-shot tool-call examples to the system prompt.") + elif avg_env_steps < 10: + print(" Root cause likely: rollouts are too short for 20-step routing.") + print(" Fix: inspect completion budget, compact tool responses, and tool-loop limits.") + else: + print(" More steps needed. Try 600-800 steps for clearer trend.") + + print("=" * 68) + + +if __name__ == "__main__": + main() diff --git a/train/smoke_test.py b/train/smoke_test.py new file mode 100644 index 0000000000000000000000000000000000000000..be3d51588e7d0199b68dac89232c9ec71fb57b17 --- /dev/null +++ b/train/smoke_test.py @@ -0,0 +1,274 @@ +""" +GRPO Smoke Test — 10 gradient steps, M4 Mac MPS (or CUDA/CPU). + +PURPOSE + Validate the full TRL training loop (model → rollout → reward → gradient) + works end-to-end with BudgetRouterGRPOEnv before a full training run. + NOT for actual learning — 10 steps is statistical noise. + +USAGE + Requires optional GRPO deps (`uv sync --extra grpo`), then e.g.: + + PYTORCH_ENABLE_MPS_FALLBACK=1 uv run python train/smoke_test.py + +EXPECTED RUNTIME + ~5-10 min on M4 Mac 48 GB (MPS, Qwen2.5-0.5B-Instruct) + +HYPERPARAMETERS (source) + - learning_rate, beta, temperature: DeepSeek-R1 GRPO paper + TRL Wordle example + - num_generations=4: minimum GRPO group; 8+ for real training + - max_completion_length=512: enough for ~10 multi-turn tool calls at 0.5B + - optim=adamw_torch: paged_adamw_8bit is CUDA-only + - No vLLM, no load_in_4bit: both CUDA-only + +PASS CRITERIA + - 10 gradient steps complete without exception + - reward_mean is a finite float (0.0 acceptable — model is untrained) + - loss is finite +""" + +from __future__ import annotations + +import math +import os +import sys +import time + +# Must be set before importing torch — causes MPS to fall back to CPU for +# unsupported Metal ops (e.g. some GRPOTrainer matmul variants). +os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1") +# Suppress tokenizer parallelism warnings +os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") + +try: + import torch + from datasets import Dataset + from peft import LoraConfig + from transformers import AutoModelForCausalLM, AutoTokenizer, TrainerCallback + from trl import GRPOConfig, GRPOTrainer +except ModuleNotFoundError as exc: + name = getattr(exc, "name", None) or str(exc) + print( + "\nGRPO smoke test requires optional packages (torch, datasets, trl, …).\n" + f"Missing: {name}\n\n" + "Install with:\n" + " uv sync --extra grpo\n\n" + "Then re-run this script.\n", + file=sys.stderr, + ) + raise SystemExit(1) from exc + +from budget_router.reward import grade_episode +from train.grpo_env import BudgetRouterGRPOEnv + +# ── Constants ──────────────────────────────────────────────────────────────── + +# Smallest Qwen2.5 with validated function-calling support. +# Smoke test only — use Qwen2.5-1.5B-Instruct for real training. +MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct" + +SYSTEM_PROMPT = ( + "You are a budget-aware API router. " + "Use the available tools to route each request to the best provider. " + "Adapt when providers degrade — switch away from failing providers early." +) + +# ── Reward function ────────────────────────────────────────────────────────── + +def reward_func(environments, **kwargs): + """ + TRL reads env instances after each rollout. Returns List[float] in [0, 1]. + grade_episode() is the calibrated grader used by the eval pipeline — keeps + training and eval metrics consistent. + """ + rewards = [] + for env in environments: + history = env._env._internal.history + if not history: + # Model made no tool calls — assign 0, not an error + rewards.append(0.0) + else: + rewards.append(float(grade_episode(history)["overall_score"])) + return rewards + +# ── Dataset ────────────────────────────────────────────────────────────────── + +def build_dataset(n: int = 32) -> Dataset: + """ + Minimal dataset. Columns become **kwargs in BudgetRouterGRPOEnv.reset(). + 'prompt' is required by GRPOTrainer (messages format). + 'scenario' and 'seed' are passed to reset() for episode configuration. + """ + return Dataset.from_list([ + { + "prompt": [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": "Route the incoming requests optimally."}, + ], + "scenario": "hard_multi", + "seed": i, + } + for i in range(n) + ]) + +# ── Step logger ────────────────────────────────────────────────────────────── + +class SmokeTestCallback(TrainerCallback): + """Captures per-step metrics for PASS/FAIL evaluation.""" + + def __init__(self): + self.steps: list[dict] = [] + + def on_log(self, args, state, control, logs=None, **kwargs): + if not logs or state.global_step == 0: + return + # TRL 1.x logs reward under "reward" or "train/reward" + reward_mean = logs.get("reward", logs.get("train/reward", float("nan"))) + reward_std = logs.get("reward_std", logs.get("train/reward_std", float("nan"))) + loss = logs.get("loss", logs.get("train/loss", float("nan"))) + entry = { + "step": state.global_step, + "reward_mean": float(reward_mean), + "reward_std": float(reward_std), + "loss": float(loss), + } + self.steps.append(entry) + print( + f" Step {entry['step']:02d}/10 | " + f"loss={entry['loss']:.4f} | " + f"reward_mean={entry['reward_mean']:.4f} | " + f"reward_std={entry['reward_std']:.4f}" + ) + +# ── Main ───────────────────────────────────────────────────────────────────── + +def main(): + t0 = time.time() + + # Device detection + if torch.backends.mps.is_available(): + device = "mps" + elif torch.cuda.is_available(): + device = "cuda" + else: + device = "cpu" + + print("=" * 62) + print("GRPO Smoke Test — Budget Router") + print("=" * 62) + print(f"Device : {device.upper()}") + print(f"Model : {MODEL_NAME}") + print(f"Steps : 10 (num_generations=4 → 40 rollouts total)") + print(f"Torch : {torch.__version__}") + if device == "cpu": + print("⚠️ WARNING: Running on CPU. Expect ~30-60 min for 10 steps.") + print("=" * 62) + + # Load model — explicit dtype for MPS (bfloat16 supported on M-series) + print("\nLoading model (may download on first run)...") + dtype = torch.bfloat16 if device in ("mps", "cuda") else torch.float32 + model = AutoModelForCausalLM.from_pretrained( + MODEL_NAME, + torch_dtype=dtype, + trust_remote_code=True, + ) + tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + # LoRA: small rank for smoke test — keeps memory and step time low + peft_config = LoraConfig( + r=8, + lora_alpha=16, + target_modules=["q_proj", "v_proj"], + lora_dropout=0.05, + bias="none", + task_type="CAUSAL_LM", + ) + + # GRPOConfig — hyperparams per TRL/OpenEnv Wordle example + DeepSeek-R1 + # Source: https://huggingface.co/docs/trl/openenv (Wordle section) + # DeepSeek-R1 paper: lr=1e-6, temp=1.0, beta=0.001 + args = GRPOConfig( + max_steps=10, + per_device_train_batch_size=1, + gradient_accumulation_steps=1, + num_generations=4, # min for GRPO; use 8 for real runs + generation_batch_size=4, # TRL 1.x: must be divisible by num_generations (see learn_experiment.py) + max_completion_length=512, # ~10 multi-turn tool-call turns + temperature=1.0, # diverse exploration (DeepSeek-R1) + beta=0.001, # KL penalty; small for verifiable tasks + learning_rate=5e-7, # conservative; real training: 1e-6 + optim="adamw_torch", # paged_adamw_8bit is CUDA-only + report_to="none", # no WandB prompt + logging_steps=1, # log every step for smoke visibility + remove_unused_columns=False, # CRITICAL: keeps scenario/seed cols for reset() + dataloader_num_workers=0, # avoid MPS multiprocessing issues + output_dir="/tmp/grpo_smoke", + ) + + dataset = build_dataset(n=32) + logger = SmokeTestCallback() + + trainer = GRPOTrainer( + model=model, + processing_class=tokenizer, + reward_funcs=reward_func, + train_dataset=dataset, + args=args, + peft_config=peft_config, + environment_factory=BudgetRouterGRPOEnv, + callbacks=[logger], + ) + + print("\nStarting training loop...\n") + try: + trainer.train() + except Exception as exc: + elapsed = time.time() - t0 + print(f"\n❌ Training loop raised {type(exc).__name__} after {elapsed:.0f}s:") + print(f" {exc}") + print("\n=== SMOKE TEST: FAIL ===") + sys.exit(1) + + elapsed = time.time() - t0 + + # Evaluate + if not logger.steps: + print("\n❌ No steps were logged — trainer may have exited early.") + print("=== SMOKE TEST: FAIL ===") + sys.exit(1) + + last = logger.steps[-1] + reward_mean = last["reward_mean"] + reward_std = last["reward_std"] + loss = last["loss"] + + passed = ( + len(logger.steps) >= 10 + and not math.isnan(reward_mean) + and not math.isnan(loss) + and not math.isinf(loss) + ) + + print("\n" + "=" * 62) + print("SMOKE TEST RESULT") + print("=" * 62) + print(f"Steps completed : {len(logger.steps)}/10") + print(f"reward_mean : {reward_mean:.4f}") + print(f"reward_std : {reward_std:.4f}") + print(f"loss : {loss:.4f}") + print(f"elapsed : {elapsed:.0f}s") + print() + if passed: + print("✅ PASS — Loop is functional. Scale up with Qwen2.5-1.5B + num_generations=8.") + else: + print("❌ FAIL — Fix issues above before full training run.") + print("=" * 62) + + if not passed: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/train/train_ppo.py b/train/train_ppo.py new file mode 100644 index 0000000000000000000000000000000000000000..91f83770b0d1ef45dc78dbd6bf2d0815429a882d --- /dev/null +++ b/train/train_ppo.py @@ -0,0 +1,84 @@ +""" +Train a PPO agent on the Easy scenario using stable-baselines3. + +Usage: + uv run python train/train_ppo.py + +Output: + trained_models/ppo_easy_50k.zip — saved SB3 model + trained_models/ppo_easy_50k/ — TensorBoard logs + +Config: + N_ENVS = 4 parallel environments + TOTAL_STEPS = 50_000 timesteps + DEVICE = mps Apple Silicon GPU (falls back to cpu if unavailable) +""" +from __future__ import annotations + +import torch +from stable_baselines3 import PPO +from stable_baselines3.common.env_util import make_vec_env +from stable_baselines3.common.callbacks import EvalCallback, StopTrainingOnRewardThreshold + +from train.gym_wrapper import BudgetRouterGymEnv +from budget_router.tasks import EASY + +# ── Config ────────────────────────────────────────────────────────────────── +N_ENVS = 4 +TOTAL_STEPS = 50_000 +SAVE_PATH = "trained_models/ppo_easy_50k" +LOG_PATH = "trained_models/ppo_easy_50k_tb" +DEVICE = "mps" if torch.backends.mps.is_available() else "cpu" +# ───────────────────────────────────────────────────────────────────────────── + + +def main() -> None: + print(f"[train] device={DEVICE} n_envs={N_ENVS} total_steps={TOTAL_STEPS:,}") + + # Vectorized training envs (4 parallel, stateless reset each episode) + train_env = make_vec_env( + lambda: BudgetRouterGymEnv(scenario=EASY), + n_envs=N_ENVS, + ) + + # Separate eval env (single, deterministic) + eval_env = BudgetRouterGymEnv(scenario=EASY, seed=99) + + # SB3 recommends stopping training once a reward threshold is hit to + # prevent over-fitting. For Easy, heuristic gets ~7.88 mean reward. + # We target > 6.0 as a sanity threshold (PPO may need more steps for parity). + stop_cb = StopTrainingOnRewardThreshold(reward_threshold=8.5, verbose=1) + eval_cb = EvalCallback( + eval_env, + callback_on_new_best=stop_cb, + eval_freq=max(5_000 // N_ENVS, 1), + n_eval_episodes=10, + verbose=1, + ) + + model = PPO( + policy="MlpPolicy", + env=train_env, + n_steps=512, # rollout buffer size per env + batch_size=64, + n_epochs=10, + gamma=0.99, + gae_lambda=0.95, + ent_coef=0.01, # small entropy bonus encourages exploration + verbose=1, + device=DEVICE, + tensorboard_log=LOG_PATH, + ) + + model.learn( + total_timesteps=TOTAL_STEPS, + callback=eval_cb, + progress_bar=True, + ) + + model.save(SAVE_PATH) + print(f"[train] Model saved → {SAVE_PATH}.zip") + + +if __name__ == "__main__": + main() diff --git a/train/train_ppo_hard_multi.py b/train/train_ppo_hard_multi.py new file mode 100644 index 0000000000000000000000000000000000000000..94d98c3e7cf19563081c31401cc49fce2e8e81d0 --- /dev/null +++ b/train/train_ppo_hard_multi.py @@ -0,0 +1,85 @@ +""" +Train a PPO agent on the Hard_Multi scenario. + +This is the key experiment: Hard_Multi has a secondary provider cascade at step 10 +(Provider B degrades after A). A reactive heuristic cannot conserve budget in advance +and scores ~0.6094. An RL agent with access to step_count + budget_remaining can +learn anticipatory routing and should materially exceed the heuristic. + +Usage: + uv run python train/train_ppo_hard_multi.py + +Output: + trained_models/ppo_hard_multi_100k.zip — saved SB3 model + trained_models/ppo_hard_multi_100k_tb/ — TensorBoard logs +""" +from __future__ import annotations + +import sys +from pathlib import Path + +# Ensure project root is on sys.path when running as a script +sys.path.insert(0, str(Path(__file__).parent.parent)) + +import torch +from stable_baselines3 import PPO +from stable_baselines3.common.env_util import make_vec_env +from stable_baselines3.common.callbacks import EvalCallback + +from train.gym_wrapper import BudgetRouterGymEnv +from budget_router.tasks import HARD_MULTI + +# ── Config ────────────────────────────────────────────────────────────────── +N_ENVS = 4 +TOTAL_STEPS = 100_000 # Hard_Multi needs more signal than Easy +SAVE_PATH = "trained_models/ppo_hard_multi_100k" +LOG_PATH = "trained_models/ppo_hard_multi_100k_tb" +DEVICE = "mps" if torch.backends.mps.is_available() else "cpu" +# ──────────────────────────────────────────────────────────────────────────── + + +def main() -> None: + print(f"[train:hard_multi] device={DEVICE} n_envs={N_ENVS} total_steps={TOTAL_STEPS:,}") + print("[train:hard_multi] Scenario: Provider A degrades step 0, Provider B degrades step 10") + print("[train:hard_multi] Heuristic baseline grader: 0.6094 (reactive, cannot conserve budget)") + + train_env = make_vec_env( + lambda: BudgetRouterGymEnv(scenario=HARD_MULTI), + n_envs=N_ENVS, + ) + + eval_env = BudgetRouterGymEnv(scenario=HARD_MULTI, seed=99) + + eval_cb = EvalCallback( + eval_env, + eval_freq=max(10_000 // N_ENVS, 1), + n_eval_episodes=10, + verbose=1, + ) + + model = PPO( + policy="MlpPolicy", + env=train_env, + n_steps=512, + batch_size=64, + n_epochs=10, + gamma=0.99, + gae_lambda=0.95, + ent_coef=0.02, # slightly higher entropy to encourage exploration on harder task + learning_rate=3e-4, + verbose=1, + device=DEVICE, + ) + + model.learn( + total_timesteps=TOTAL_STEPS, + callback=eval_cb, + progress_bar=True, + ) + + model.save(SAVE_PATH) + print(f"[train:hard_multi] Model saved → {SAVE_PATH}.zip") + + +if __name__ == "__main__": + main() diff --git a/train_sft.py b/train_sft.py new file mode 100644 index 0000000000000000000000000000000000000000..c5e4426d6e4f0529feb418afc2116ef333c16d24 --- /dev/null +++ b/train_sft.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# /// script +# dependencies = [ +# "torch", +# "transformers>=4.45.0", +# "trl>=0.25.0", +# "peft>=0.13.0", +# "datasets>=2.20.0", +# "accelerate>=0.34.0", +# "huggingface_hub>=0.24.0", +# ] +# /// +"""Train a LoRA SFT Budget Router model on HF Jobs and push merged weights.""" + +from __future__ import annotations + +import argparse +import os + + +DEFAULT_BASE_MODEL = "Qwen/Qwen2.5-1.5B-Instruct" +DEFAULT_DATASET_REPO = "akshay4/budget-router-sft-data" +DEFAULT_OUTPUT_REPO = "akshay4/budget-router-sft-qwen1.5b" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Train Budget Router SFT model.") + parser.add_argument("--base-model", default=os.getenv("BASE_MODEL", DEFAULT_BASE_MODEL)) + parser.add_argument("--dataset-repo", default=os.getenv("DATASET_REPO", DEFAULT_DATASET_REPO)) + parser.add_argument("--output-repo", default=os.getenv("OUTPUT_REPO", DEFAULT_OUTPUT_REPO)) + parser.add_argument("--num-epochs", type=float, default=float(os.getenv("NUM_EPOCHS", "3"))) + parser.add_argument("--learning-rate", type=float, default=float(os.getenv("LEARNING_RATE", "2e-4"))) + parser.add_argument("--lora-r", type=int, default=int(os.getenv("LORA_R", "16"))) + parser.add_argument("--lora-alpha", type=int, default=int(os.getenv("LORA_ALPHA", "32"))) + parser.add_argument("--max-length", type=int, default=int(os.getenv("MAX_SEQ_LENGTH", "4096"))) + parser.add_argument("--batch-size", type=int, default=int(os.getenv("PER_DEVICE_BATCH_SIZE", "2"))) + parser.add_argument("--grad-accum", type=int, default=int(os.getenv("GRADIENT_ACCUMULATION_STEPS", "4"))) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + token = os.environ.get("HF_TOKEN") + if not token: + raise RuntimeError("HF_TOKEN must be set as a secret in the HF Job.") + + import torch + from datasets import load_dataset + from peft import LoraConfig + from transformers import AutoModelForCausalLM, AutoTokenizer + from trl import SFTConfig, SFTTrainer + + device_supports_bf16 = torch.cuda.is_available() and torch.cuda.is_bf16_supported() + dtype = torch.bfloat16 if device_supports_bf16 else torch.float16 + + print(f"[train-sft] loading dataset={args.dataset_repo}", flush=True) + dataset = load_dataset(args.dataset_repo, split="train", token=token) + print(f"[train-sft] rows={len(dataset)}", flush=True) + + print(f"[train-sft] loading base_model={args.base_model}", flush=True) + model = AutoModelForCausalLM.from_pretrained( + args.base_model, + torch_dtype=dtype, + token=token, + ) + tokenizer = AutoTokenizer.from_pretrained(args.base_model, token=token) + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + peft_config = LoraConfig( + r=args.lora_r, + lora_alpha=args.lora_alpha, + target_modules=[ + "q_proj", + "k_proj", + "v_proj", + "o_proj", + "gate_proj", + "up_proj", + "down_proj", + ], + lora_dropout=0.05, + bias="none", + task_type="CAUSAL_LM", + ) + + sft_args = SFTConfig( + output_dir="./sft_output", + num_train_epochs=args.num_epochs, + per_device_train_batch_size=args.batch_size, + gradient_accumulation_steps=args.grad_accum, + learning_rate=args.learning_rate, + lr_scheduler_type="cosine", + warmup_ratio=0.1, + bf16=device_supports_bf16, + fp16=not device_supports_bf16, + optim="adamw_torch", + logging_steps=5, + save_strategy="epoch", + report_to="none", + max_length=args.max_length, + packing=False, + assistant_only_loss=True, + push_to_hub=False, + ) + + trainer = SFTTrainer( + model=model, + processing_class=tokenizer, + args=sft_args, + train_dataset=dataset, + peft_config=peft_config, + ) + trainable = getattr(trainer.model, "print_trainable_parameters", None) + if callable(trainable): + trainable() + + print("[train-sft] starting training", flush=True) + train_result = trainer.train() + print(f"[train-sft] train_metrics={train_result.metrics}", flush=True) + + final_loss = train_result.metrics.get("train_loss") + if final_loss is not None and float(final_loss) > 0.5: + print("[train-sft] WARNING: train_loss > 0.5; inspect data and consider more epochs.", flush=True) + + print("[train-sft] merging LoRA and pushing model", flush=True) + merged = trainer.model.merge_and_unload() if hasattr(trainer.model, "merge_and_unload") else trainer.model + merged.push_to_hub(args.output_repo, token=token) + tokenizer.push_to_hub(args.output_repo, token=token) + print(f"[train-sft] Model pushed to {args.output_repo}. Training complete.", flush=True) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000000000000000000000000000000000000..d151d95801eb117f58d9ecdc98ac02eb8ce65ab4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,4301 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "accelerate" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835, upload-time = "2026-03-04T19:34:12.359Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, +] + +[[package]] +name = "aiofiles" +version = "23.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/41/cfed10bc64d774f497a86e5ede9248e1d062db675504b41c320954d99641/aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a", size = 32072, upload-time = "2023-08-09T15:23:11.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/19/5af6804c4cc0fed83f47bff6e413a98a36618e7d40185cd36e69737f3b0e/aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107", size = 15727, upload-time = "2023-08-09T15:23:09.774Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/85/cebc47ee74d8b408749073a1a46c6fcba13d170dc8af7e61996c6c9394ac/aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b", size = 750547, upload-time = "2026-03-31T21:56:30.024Z" }, + { url = "https://files.pythonhosted.org/packages/05/98/afd308e35b9d3d8c9ec54c0918f1d722c86dc17ddfec272fcdbcce5a3124/aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5", size = 503535, upload-time = "2026-03-31T21:56:31.935Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4d/926c183e06b09d5270a309eb50fbde7b09782bfd305dec1e800f329834fb/aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670", size = 497830, upload-time = "2026-03-31T21:56:33.654Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d6/f47d1c690f115a5c2a5e8938cce4a232a5be9aac5c5fb2647efcbbbda333/aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274", size = 1682474, upload-time = "2026-03-31T21:56:35.513Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/056fd37b1bb52eac760303e5196acc74d9d546631b035704ae5927f7b4ac/aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a", size = 1655259, upload-time = "2026-03-31T21:56:37.843Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/78eb1a20c1c28ae02f6a3c0f4d7b0dcc66abce5290cadd53d78ce3084175/aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d", size = 1736204, upload-time = "2026-03-31T21:56:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/de/6c/d20d7de23f0b52b8c1d9e2033b2db1ac4dacbb470bb74c56de0f5f86bb4f/aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796", size = 1826198, upload-time = "2026-03-31T21:56:41.378Z" }, + { url = "https://files.pythonhosted.org/packages/2f/86/a6f3ff1fd795f49545a7c74b2c92f62729135d73e7e4055bf74da5a26c82/aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95", size = 1681329, upload-time = "2026-03-31T21:56:43.374Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/84cd3dab6b7b4f3e6fe9459a961acb142aaab846417f6e8905110d7027e5/aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5", size = 1560023, upload-time = "2026-03-31T21:56:45.031Z" }, + { url = "https://files.pythonhosted.org/packages/41/2c/db61b64b0249e30f954a65ab4cb4970ced57544b1de2e3c98ee5dc24165f/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a", size = 1652372, upload-time = "2026-03-31T21:56:47.075Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/e96988a6c982d047810c772e28c43c64c300c943b0ed5c1c0c4ce1e1027c/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73", size = 1662031, upload-time = "2026-03-31T21:56:48.835Z" }, + { url = "https://files.pythonhosted.org/packages/b7/26/a56feace81f3d347b4052403a9d03754a0ab23f7940780dada0849a38c92/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297", size = 1708118, upload-time = "2026-03-31T21:56:50.833Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/b6173a8ff03d01d5e1a694bc06764b5dad1df2d4ed8f0ceec12bb3277936/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074", size = 1548667, upload-time = "2026-03-31T21:56:52.81Z" }, + { url = "https://files.pythonhosted.org/packages/16/13/13296ffe2c132d888b3fe2c195c8b9c0c24c89c3fa5cc2c44464dc23b22e/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e", size = 1724490, upload-time = "2026-03-31T21:56:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1f1c287f4a79782ef36e5a6e62954c85343bc30470d862d30bd5f26c9fa2/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7", size = 1667109, upload-time = "2026-03-31T21:56:56.21Z" }, + { url = "https://files.pythonhosted.org/packages/ef/42/8461a2aaf60a8f4ea4549a4056be36b904b0eb03d97ca9a8a2604681a500/aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9", size = 439478, upload-time = "2026-03-31T21:56:58.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/71/06956304cb5ee439dfe8d86e1b2e70088bd88ed1ced1f42fb29e5d855f0e/aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76", size = 462047, upload-time = "2026-03-31T21:57:00.257Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/a20c4ac64aeaef1679e25c9983573618ff765d7aa829fa2b84ae7573169e/aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6", size = 757513, upload-time = "2026-03-31T21:57:02.146Z" }, + { url = "https://files.pythonhosted.org/packages/75/0a/39fa6c6b179b53fcb3e4b3d2b6d6cad0180854eda17060c7218540102bef/aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d", size = 506748, upload-time = "2026-03-31T21:57:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c", size = 501673, upload-time = "2026-03-31T21:57:06.208Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/3bc7525d7e2beaa11b309a70d48b0d3cfc3c2089ec6a7d0820d59c657053/aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb", size = 1763757, upload-time = "2026-03-31T21:57:07.882Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ab/e87744cf18f1bd78263aba24924d4953b41086bd3a31d22452378e9028a0/aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6", size = 1720152, upload-time = "2026-03-31T21:57:09.946Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/ed17a6f2d742af17b50bae2d152315ed1b164b07a5fd5cc1754d99e4dfa5/aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13", size = 1818010, upload-time = "2026-03-31T21:57:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/53/06/ecbc63dc937192e2a5cb46df4d3edb21deb8225535818802f210a6ea5816/aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174", size = 1907251, upload-time = "2026-03-31T21:57:14.023Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc", size = 1759969, upload-time = "2026-03-31T21:57:16.146Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/a38f8c9105199dd3b9706745865a8a59d0041b6be0ca0cc4b2ccf1bab374/aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6", size = 1616871, upload-time = "2026-03-31T21:57:17.856Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/27392a61ead8ab38072105c71aa44ff891e71653fe53d576a7067da2b4e8/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49", size = 1739844, upload-time = "2026-03-31T21:57:19.679Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/5564e7ae26d94f3214250009a0b1c65a0c6af4bf88924ccb6fdab901de28/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8", size = 1731969, upload-time = "2026-03-31T21:57:22.006Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/705a3929149865fc941bcbdd1047b238e4a72bcb215a9b16b9d7a2e8d992/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d", size = 1795193, upload-time = "2026-03-31T21:57:24.256Z" }, + { url = "https://files.pythonhosted.org/packages/a6/19/edabed62f718d02cff7231ca0db4ef1c72504235bc467f7b67adb1679f48/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c", size = 1606477, upload-time = "2026-03-31T21:57:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/de/fc/76f80ef008675637d88d0b21584596dc27410a990b0918cb1e5776545b5b/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac", size = 1813198, upload-time = "2026-03-31T21:57:28.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/67/5b3ac26b80adb20ea541c487f73730dc8fa107d632c998f25bbbab98fcda/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3", size = 1752321, upload-time = "2026-03-31T21:57:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/88/06/e4a2e49255ea23fa4feeb5ab092d90240d927c15e47b5b5c48dff5a9ce29/aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06", size = 439069, upload-time = "2026-03-31T21:57:32.388Z" }, + { url = "https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8", size = 462859, upload-time = "2026-03-31T21:57:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" }, + { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" }, + { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" }, + { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" }, + { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, + { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, + { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, + { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, + { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, + { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, + { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, + { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, +] + +[[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.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[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 = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "bitsandbytes" +version = "0.49.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/7d/f1fe0992334b18cd8494f89aeec1dcc674635584fcd9f115784fea3a1d05/bitsandbytes-0.49.2-py3-none-macosx_14_0_arm64.whl", hash = "sha256:87be5975edeac5396d699ecbc39dfc47cf2c026daaf2d5852a94368611a6823f", size = 131940, upload-time = "2026-02-16T21:26:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/acff7af06c818664aa87ff73e17a52c7788ad746b72aea09d3cb8e424348/bitsandbytes-0.49.2-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:2fc0830c5f7169be36e60e11f2be067c8f812dfcb829801a8703735842450750", size = 31442815, upload-time = "2026-02-16T21:26:06.783Z" }, + { url = "https://files.pythonhosted.org/packages/19/57/3443d6f183436fbdaf5000aac332c4d5ddb056665d459244a5608e98ae92/bitsandbytes-0.49.2-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:54b771f06e1a3c73af5c7f16ccf0fc23a846052813d4b008d10cb6e017dd1c8c", size = 60651714, upload-time = "2026-02-16T21:26:11.579Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d4/501655842ad6771fb077f576d78cbedb5445d15b1c3c91343ed58ca46f0e/bitsandbytes-0.49.2-py3-none-win_amd64.whl", hash = "sha256:2e0ddd09cd778155388023cbe81f00afbb7c000c214caef3ce83386e7144df7d", size = 55372289, upload-time = "2026-02-16T21:26:16.267Z" }, +] + +[[package]] +name = "brotli" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/10/a090475284fc4a71aed40a96f32e44a7fe5bda39687353dd977720b211b6/brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e", size = 863089, upload-time = "2025-11-05T18:38:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/03/41/17416630e46c07ac21e378c3464815dd2e120b441e641bc516ac32cc51d2/brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984", size = 445442, upload-time = "2025-11-05T18:38:02.434Z" }, + { url = "https://files.pythonhosted.org/packages/24/31/90cc06584deb5d4fcafc0985e37741fc6b9717926a78674bbb3ce018957e/brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de", size = 1532658, upload-time = "2025-11-05T18:38:03.588Z" }, + { url = "https://files.pythonhosted.org/packages/62/17/33bf0c83bcbc96756dfd712201d87342732fad70bb3472c27e833a44a4f9/brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947", size = 1631241, upload-time = "2025-11-05T18:38:04.582Z" }, + { url = "https://files.pythonhosted.org/packages/48/10/f47854a1917b62efe29bc98ac18e5d4f71df03f629184575b862ef2e743b/brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2", size = 1424307, upload-time = "2025-11-05T18:38:05.587Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b7/f88eb461719259c17483484ea8456925ee057897f8e64487d76e24e5e38d/brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84", size = 1488208, upload-time = "2025-11-05T18:38:06.613Z" }, + { url = "https://files.pythonhosted.org/packages/26/59/41bbcb983a0c48b0b8004203e74706c6b6e99a04f3c7ca6f4f41f364db50/brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d", size = 1597574, upload-time = "2025-11-05T18:38:07.838Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e6/8c89c3bdabbe802febb4c5c6ca224a395e97913b5df0dff11b54f23c1788/brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1", size = 1492109, upload-time = "2025-11-05T18:38:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/4b19d4310b2dbd545c0c33f176b0528fa68c3cd0754e34b2f2bcf56548ae/brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997", size = 334461, upload-time = "2025-11-05T18:38:10.729Z" }, + { url = "https://files.pythonhosted.org/packages/ac/39/70981d9f47705e3c2b95c0847dfa3e7a37aa3b7c6030aedc4873081ed005/brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196", size = 369035, upload-time = "2025-11-05T18:38:11.827Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110, upload-time = "2025-11-05T18:38:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438, upload-time = "2025-11-05T18:38:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420, upload-time = "2025-11-05T18:38:15.111Z" }, + { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619, upload-time = "2025-11-05T18:38:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014, upload-time = "2025-11-05T18:38:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661, upload-time = "2025-11-05T18:38:18.41Z" }, + { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150, upload-time = "2025-11-05T18:38:19.792Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505, upload-time = "2025-11-05T18:38:20.913Z" }, + { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451, upload-time = "2025-11-05T18:38:21.94Z" }, + { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035, upload-time = "2025-11-05T18:38:22.941Z" }, + { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" }, + { url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, + { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" }, +] + +[[package]] +name = "budget-router" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "fastapi" }, + { name = "gradio" }, + { name = "huggingface-hub" }, + { name = "jmespath" }, + { name = "openai" }, + { name = "openenv-core" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "starlette" }, + { name = "typer" }, + { name = "uvicorn" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] +grpo = [ + { name = "accelerate" }, + { name = "bitsandbytes" }, + { name = "datasets" }, + { name = "huggingface-hub" }, + { name = "peft" }, + { name = "torch" }, + { name = "transformers" }, + { name = "trl" }, +] +training = [ + { name = "gymnasium" }, + { name = "stable-baselines3" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "python-docx" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", marker = "extra == 'grpo'", specifier = ">=0.30.0" }, + { name = "bitsandbytes", marker = "extra == 'grpo'", specifier = ">=0.43.0" }, + { name = "datasets", marker = "extra == 'grpo'", specifier = ">=2.18.0" }, + { name = "fastapi", specifier = ">=0.110,<1.0" }, + { name = "gradio", specifier = ">=5.0,<6.0" }, + { name = "gymnasium", marker = "extra == 'training'", specifier = ">=1.0.0,<2.0" }, + { name = "huggingface-hub", specifier = ">=1.12.0" }, + { name = "huggingface-hub", marker = "extra == 'grpo'", specifier = ">=1.5.0" }, + { name = "jmespath", specifier = ">=1.1.0" }, + { name = "openai", specifier = ">=1.0,<2.0" }, + { name = "openenv-core", specifier = ">=0.1.0" }, + { name = "peft", marker = "extra == 'grpo'", specifier = ">=0.10.0" }, + { name = "pydantic", specifier = ">=2.0,<3.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" }, + { name = "requests", specifier = ">=2.31,<3.0" }, + { name = "stable-baselines3", marker = "extra == 'training'", specifier = ">=2.0,<3.0" }, + { name = "starlette", specifier = "<1.0" }, + { name = "torch", marker = "extra == 'grpo'", specifier = ">=2.1.0" }, + { name = "transformers", marker = "extra == 'grpo'", specifier = ">=5.3.0" }, + { name = "trl", marker = "extra == 'grpo'", specifier = ">=0.15.0" }, + { name = "typer", specifier = ">=0.12,<1.0" }, + { name = "uvicorn", specifier = ">=0.30,<1.0" }, +] +provides-extras = ["dev", "training", "grpo"] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.2" }, + { name = "python-docx", specifier = ">=1.2.0" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" }, + { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[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 = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/fe/7351d7e586a8b4c9f89731bfe4cf0148223e8f9903ff09571f78b3fb0682/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556", size = 5744254, upload-time = "2026-03-11T00:12:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ef/184aa775e970fc089942cd9ec6302e6e44679d4c14549c6a7ea45bf7f798/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6f3682ec3c4769326aafc67c2ba669d97d688d0b7e63e659d36d2f8b72f32d6", size = 6329075, upload-time = "2026-03-11T00:12:32.319Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/74/8c66861b873d8eed51fde56d3091baa4906a56f0d4390cae991f2d41dda5/cuda_pathfinder-1.5.1-py3-none-any.whl", hash = "sha256:b3718097fb57cf9e8a904dd072d806f2c9a27627e35c020b06ab9454bcec08c0", size = 49861, upload-time = "2026-04-03T16:41:22.203Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "datasets" +version = "4.8.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/22/73e46ac7a8c25e7ef0b3bd6f10da3465021d90219a32eb0b4d2afea4c56e/datasets-4.8.4.tar.gz", hash = "sha256:a1429ed853275ce7943a01c6d2e25475b4501eb758934362106a280470df3a52", size = 604382, upload-time = "2026-03-23T14:21:17.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/e5/247d094108e42ac26363ab8dc57f168840cf7c05774b40ffeb0d78868fcc/datasets-4.8.4-py3-none-any.whl", hash = "sha256:cdc8bee4698e549d78bf1fed6aea2eebc760b22b084f07e6fc020c6577a6ce6d", size = 526991, upload-time = "2026-03-23T14:21:15.89Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "farama-notifications" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/2c/8384832b7a6b1fd6ba95bbdcae26e7137bb3eedc955c42fd5cdcc086cfbf/Farama-Notifications-0.0.4.tar.gz", hash = "sha256:13fceff2d14314cf80703c8266462ebf3733c7d165336eee998fc58e545efd18", size = 2131, upload-time = "2023-02-27T18:28:41.047Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl", hash = "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae", size = 2511, upload-time = "2023-02-27T18:28:39.447Z" }, +] + +[[package]] +name = "fastapi" +version = "0.135.2" +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/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833, upload-time = "2026-03-23T14:12:41.697Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407, upload-time = "2026-03-23T14:12:43.284Z" }, +] + +[[package]] +name = "ffmpy" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/d2/1c4c582d71bcc65c76fa69fab85de6257d50fdf6fd4a2317c53917e9a581/ffmpy-1.0.0.tar.gz", hash = "sha256:b12932e95435c8820f1cd041024402765f821971e4bae753b327fc02a6e12f8b", size = 5101, upload-time = "2025-11-11T06:24:23.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/56/dd3669eccebb6d8ac81e624542ebd53fe6f08e1b8f2f8d50aeb7e3b83f99/ffmpy-1.0.0-py3-none-any.whl", hash = "sha256:5640e5f0fd03fb6236d0e119b16ccf6522db1c826fdf35dcb87087b60fd7504f", size = 5614, upload-time = "2025-11-11T06:24:22.818Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, +] + +[[package]] +name = "fonttools" +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, + { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, + { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +] + +[[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/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { 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/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { 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.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "gradio" +version = "5.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/04/8daf96bd6d2470f03e2a15a9fc900c7ecf6549619173f16c5944c7ec15a7/gradio-5.50.0-py3-none-any.whl", hash = "sha256:d06770d57cdda9b703ef9cf767ac93a890a0e12d82679a310eef74203a3673f4", size = 63530991, upload-time = "2025-11-21T18:07:19.239Z" }, +] + +[[package]] +name = "gradio-client" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/f2a47134c5b5a7f3bad27eae749589a80d81efaaad8f59af47c136712bf6/gradio_client-1.14.0-py3-none-any.whl", hash = "sha256:9a2f5151978411e0f8b55a2d38cddd0a94491851149d14db4af96f5a09774825", size = 325555, upload-time = "2025-11-21T18:04:21.834Z" }, +] + +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + +[[package]] +name = "gymnasium" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl", hash = "sha256:e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373", size = 952113, upload-time = "2025-12-18T16:51:08.445Z" }, +] + +[[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 = "hf-xet" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/92/ec9ad04d0b5728dca387a45af7bc98fbb0d73b2118759f5f6038b61a57e8/hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113", size = 670477, upload-time = "2026-03-31T22:40:07.874Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/43/724d307b34e353da0abd476e02f72f735cdd2bc86082dee1b32ea0bfee1d/hf_xet-1.4.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7551659ba4f1e1074e9623996f28c3873682530aee0a846b7f2f066239228144", size = 3800935, upload-time = "2026-03-31T22:39:49.618Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d2/8bee5996b699262edb87dbb54118d287c0e1b2fc78af7cdc41857ba5e3c4/hf_xet-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bee693ada985e7045997f05f081d0e12c4c08bd7626dc397f8a7c487e6c04f7f", size = 3558942, upload-time = "2026-03-31T22:39:47.938Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a1/e993d09cbe251196fb60812b09a58901c468127b7259d2bf0f68bf6088eb/hf_xet-1.4.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21644b404bb0100fe3857892f752c4d09642586fd988e61501c95bbf44b393a3", size = 4207657, upload-time = "2026-03-31T22:39:39.69Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/9eb6d21e5c34c63e5e399803a6932fa983cabdf47c0ecbcfe7ea97684b8c/hf_xet-1.4.3-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:987f09cfe418237812896a6736b81b1af02a3a6dcb4b4944425c4c4fca7a7cf8", size = 3986765, upload-time = "2026-03-31T22:39:37.936Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/8ad6f16fdb82f5f7284a34b5ec48645bd575bdcd2f6f0d1644775909c486/hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:60cf7fc43a99da0a853345cf86d23738c03983ee5249613a6305d3e57a5dca74", size = 4188162, upload-time = "2026-03-31T22:39:58.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c4/39d6e136cbeea9ca5a23aad4b33024319222adbdc059ebcda5fc7d9d5ff4/hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2815a49a7a59f3e2edf0cf113ae88e8cb2ca2a221bf353fb60c609584f4884d4", size = 4424525, upload-time = "2026-03-31T22:40:00.225Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/adc32dae6bdbc367853118b9878139ac869419a4ae7ba07185dc31251b76/hf_xet-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:42ee323265f1e6a81b0e11094564fb7f7e0ec75b5105ffd91ae63f403a11931b", size = 3671610, upload-time = "2026-03-31T22:40:10.42Z" }, + { url = "https://files.pythonhosted.org/packages/e2/19/25d897dcc3f81953e0c2cde9ec186c7a0fee413eb0c9a7a9130d87d94d3a/hf_xet-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:27c976ba60079fb8217f485b9c5c7fcd21c90b0367753805f87cb9f3cdc4418a", size = 3528529, upload-time = "2026-03-31T22:40:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/ec/36/3e8f85ca9fe09b8de2b2e10c63b3b3353d7dda88a0b3d426dffbe7b8313b/hf_xet-1.4.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5251d5ece3a81815bae9abab41cf7ddb7bcb8f56411bce0827f4a3071c92fdc6", size = 3801019, upload-time = "2026-03-31T22:39:56.651Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9c/defb6cb1de28bccb7bd8d95f6e60f72a3d3fa4cb3d0329c26fb9a488bfe7/hf_xet-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1feb0f3abeacee143367c326a128a2e2b60868ec12a36c225afb1d6c5a05e6d2", size = 3558746, upload-time = "2026-03-31T22:39:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/8d001191893178ff8e826e46ad5299446e62b93cd164e17b0ffea08832ec/hf_xet-1.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b301fc150290ca90b4fccd079829b84bb4786747584ae08b94b4577d82fb791", size = 4207692, upload-time = "2026-03-31T22:39:46.246Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/6790b402803250e9936435613d3a78b9aaeee7973439f0918848dde58309/hf_xet-1.4.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d972fbe95ddc0d3c0fc49b31a8a69f47db35c1e3699bf316421705741aab6653", size = 3986281, upload-time = "2026-03-31T22:39:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/ea62552fe53db652a9099eda600b032d75554d0e86c12a73824bfedef88b/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5b48db1ee344a805a1b9bd2cda9b6b65fe77ed3787bd6e87ad5521141d317cd", size = 4187414, upload-time = "2026-03-31T22:40:04.951Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f5/bc1456d4638061bea997e6d2db60a1a613d7b200e0755965ec312dc1ef79/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22bdc1f5fb8b15bf2831440b91d1c9bbceeb7e10c81a12e8d75889996a5c9da8", size = 4424368, upload-time = "2026-03-31T22:40:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/ab597bae87e1f06d18d3ecb8ed7f0d3c9a37037fc32ce76233d369273c64/hf_xet-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0392c79b7cf48418cd61478c1a925246cf10639f4cd9d94368d8ca1e8df9ea07", size = 3672280, upload-time = "2026-03-31T22:40:16.401Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/2e462d34e23a09a74d73785dbed71cc5dbad82a72eee2ad60a72a554155d/hf_xet-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:681c92a07796325778a79d76c67011764ecc9042a8c3579332b61b63ae512075", size = 3528945, upload-time = "2026-03-31T22:40:14.995Z" }, + { url = "https://files.pythonhosted.org/packages/ac/9f/9c23e4a447b8f83120798f9279d0297a4d1360bdbf59ef49ebec78fe2545/hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025", size = 3805048, upload-time = "2026-03-31T22:39:53.105Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f8/7aacb8e5f4a7899d39c787b5984e912e6c18b11be136ef13947d7a66d265/hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583", size = 3562178, upload-time = "2026-03-31T22:39:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/df/9a/a24b26dc8a65f0ecc0fe5be981a19e61e7ca963b85e062c083f3a9100529/hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08", size = 4212320, upload-time = "2026-03-31T22:39:42.922Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/46d493db155d2ee2801b71fb1b0fd67696359047fdd8caee2c914cc50c79/hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f", size = 3991546, upload-time = "2026-03-31T22:39:41.335Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f5/067363e1c96c6b17256910830d1b54099d06287e10f4ec6ec4e7e08371fc/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac", size = 4193200, upload-time = "2026-03-31T22:40:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/42/4b/53951592882d9c23080c7644542fda34a3813104e9e11fa1a7d82d419cb8/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba", size = 4429392, upload-time = "2026-03-31T22:40:03.492Z" }, + { url = "https://files.pythonhosted.org/packages/8a/21/75a6c175b4e79662ad8e62f46a40ce341d8d6b206b06b4320d07d55b188c/hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021", size = 3677359, upload-time = "2026-03-31T22:40:13.619Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7c/44314ecd0e89f8b2b51c9d9e5e7a60a9c1c82024ac471d415860557d3cd8/hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47", size = 3533664, upload-time = "2026-03-31T22:40:12.152Z" }, +] + +[[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 = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[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.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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 = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/52/1b54cb569509c725a32c1315261ac9fd0e6b91bbbf74d86fca10d3376164/huggingface_hub-1.12.0.tar.gz", hash = "sha256:7c3fe85e24b652334e5d456d7a812cd9a071e75630fac4365d9165ab5e4a34b6", size = 763091, upload-time = "2026-04-24T13:32:08.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/2b/ef03ddb96bd1123503c2bd6932001020292deea649e9bf4caa2cb65a85bf/huggingface_hub-1.12.0-py3-none-any.whl", hash = "sha256:d74939969585ee35748bd66de09baf84099d461bda7287cd9043bfb99b0e424d", size = 646806, upload-time = "2026-04-24T13:32:06.717Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[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 = "jiter" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + +[[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 = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "lxml" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/6e/ee8fc0e01202eb3dd2b9e1ea4f0910d72425d35c66187c63931d7a3ea73f/lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec", size = 8540733, upload-time = "2026-04-18T04:27:33.185Z" }, + { url = "https://files.pythonhosted.org/packages/54/e8/325fe9b942824c773dffe1baf0c35b046a763851fdff4393af4450bceeb7/lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec", size = 4602805, upload-time = "2026-04-18T04:27:36.097Z" }, + { url = "https://files.pythonhosted.org/packages/2d/81/221aa3ea4a40370bb0358fa454cbe7e5a837e522f7630c24dfef3f9a73b0/lxml-6.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551", size = 5002652, upload-time = "2026-04-18T04:27:30.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e1/fdbfb9019542f1875c093576df7f37adc2983c8ba7ecf17e5f14490bc107/lxml-6.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd", size = 5155332, upload-time = "2026-04-18T04:27:33.507Z" }, + { url = "https://files.pythonhosted.org/packages/56/b1/4087c782fff397cd03abf9c551069be59bb04a7e548c50fb7b9c4cdaca28/lxml-6.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215", size = 5057226, upload-time = "2026-04-18T04:27:37.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/66/516c79dec8417f3a972327330254c0b5fac93d5c3ecfd8a5b43650a5a4d9/lxml-6.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4", size = 5287588, upload-time = "2026-04-18T04:27:41.4Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/e578f4cbeb42b9df9f29b0d44a45a7cdfa3a5ae300dd59ec68e3602d29bb/lxml-6.1.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea", size = 5412438, upload-time = "2026-04-18T04:27:45.589Z" }, + { url = "https://files.pythonhosted.org/packages/47/5b/2aa68307d6d15959e84d4882f9c04f2da63127eac463e1594166f681ef77/lxml-6.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6", size = 4770997, upload-time = "2026-04-18T04:27:49.853Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c9/3e51fc1228310a836b4eb32595ae00154ab12197fca944676a3ab3b163ea/lxml-6.1.0-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3", size = 5359678, upload-time = "2026-04-18T04:31:56.184Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/ab8bc834f977fbbd310e697b120787c153db026f9151e02a88d2645d4e5b/lxml-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7", size = 5107890, upload-time = "2026-04-18T04:32:00.387Z" }, + { url = "https://files.pythonhosted.org/packages/bb/10/8a143cfa3ac99cb5b0523ff6d0429a9c9dddf25ffeae09caa3866c7964d9/lxml-6.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16", size = 4803977, upload-time = "2026-04-18T04:32:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/45/fd/ee02faf52fa39c2fe32f824628958b9aa86dff21343dc3161f0e3c6ccd15/lxml-6.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1", size = 5350277, upload-time = "2026-04-18T04:32:09.176Z" }, + { url = "https://files.pythonhosted.org/packages/85/8c/b3481364b8554b5d36d540189a87fc71e94b0b01c24f8f152bd662dd2e45/lxml-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a", size = 5309717, upload-time = "2026-04-18T04:32:13.303Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/a6b21927077a9127afa17473b6576b322616f34ac50ee4f577e763b75ec0/lxml-6.1.0-cp310-cp310-win32.whl", hash = "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544", size = 3598491, upload-time = "2026-04-18T04:27:24.288Z" }, + { url = "https://files.pythonhosted.org/packages/ea/82/14dea800d041274d96c07d49ff9191f011d1427450850de19bf541e2cc12/lxml-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a", size = 4020906, upload-time = "2026-04-18T04:27:27.53Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ba/d3539aaf4d9d21456b9a7b902816623227d05d63e7c5aafd8834c4b9bed6/lxml-6.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776", size = 3667787, upload-time = "2026-04-18T04:27:29.407Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", size = 8526232, upload-time = "2026-04-18T04:27:40.389Z" }, + { url = "https://files.pythonhosted.org/packages/a7/51/adc8826570a112f83bb4ddb3a2ab510bbc2ccd62c1b9fe1f34fae2d90b57/lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", size = 4595448, upload-time = "2026-04-18T04:27:44.208Z" }, + { url = "https://files.pythonhosted.org/packages/54/84/5a9ec07cbe1d2334a6465f863b949a520d2699a755738986dcd3b6b89e3f/lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", size = 4923771, upload-time = "2026-04-18T04:32:17.402Z" }, + { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, + { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, + { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, + { url = "https://files.pythonhosted.org/packages/9a/da/bc710fad8bf04b93baee752c192eaa2210cd3a84f969d0be7830fea55802/lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", size = 5329999, upload-time = "2026-04-18T04:32:34.019Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, + { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, + { url = "https://files.pythonhosted.org/packages/34/20/c7852904858b4723af01d2fc14b5d38ff57cb92f01934a127ebd9a9e51aa/lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", size = 3594026, upload-time = "2026-04-18T04:27:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", size = 4025114, upload-time = "2026-04-18T04:27:34.077Z" }, + { url = "https://files.pythonhosted.org/packages/c2/df/c84dcc175fd690823436d15b41cb920cd5ba5e14cd8bfb00949d5903b320/lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", size = 3667742, upload-time = "2026-04-18T04:27:38.45Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, + { url = "https://files.pythonhosted.org/packages/f2/88/55143966481409b1740a3ac669e611055f49efd68087a5ce41582325db3e/lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", size = 3930134, upload-time = "2026-04-18T04:32:35.008Z" }, + { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, + { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/32/a2/4ac7eb32a4d997dd352c32c32399aae27b3f268d440e6f9cfa405b575d2f/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", size = 4251124, upload-time = "2026-04-18T04:36:09.056Z" }, + { url = "https://files.pythonhosted.org/packages/33/ef/d6abd850bb4822f9b720cfe36b547a558e694881010ff7d012191e8769c6/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", size = 4401758, upload-time = "2026-04-18T04:36:11.803Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[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 = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, + { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, + { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.0.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "openai" +version = "1.109.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, +] + +[[package]] +name = "openenv-core" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "huggingface-hub" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "tomli" }, + { name = "tomli-w" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/38/cacf28d5adeb5c24fc9df28da32f5eb7c0e3891b60b6af1d0ef943e3be69/openenv_core-0.1.1.tar.gz", hash = "sha256:43e0aa8ff3cef99bd62a5b4f8f5db707f9bdf42ba3c1eb48465feb78135a91f8", size = 53632, upload-time = "2025-11-26T06:04:10.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/3e/408848d10fa3fa02c512baeea93e817c777819f8e43ae2ad0ef47022d0e3/openenv_core-0.1.1-py3-none-any.whl", hash = "sha256:cad04688172e068b5a81e32b4d83cb6886c18a0e3cf5cbc8e8c60430753df182", size = 70148, upload-time = "2025-11-26T06:04:08.839Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/1b/2024d06792d0779f9dbc51531b61c24f76c75b9f4ce05e6f3377a1814cea/orjson-3.11.8.tar.gz", hash = "sha256:96163d9cdc5a202703e9ad1b9ae757d5f0ca62f4fa0cc93d1f27b0e180cc404e", size = 5603832, upload-time = "2026-03-31T16:16:27.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/90/5d81f61fe3e4270da80c71442864c091cee3003cc8984c75f413fe742a07/orjson-3.11.8-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e6693ff90018600c72fd18d3d22fa438be26076cd3c823da5f63f7bab28c11cb", size = 229663, upload-time = "2026-03-31T16:14:30.708Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/85e06b0eb11de6fb424120fd5788a07035bd4c5e6bb7841ae9972a0526d1/orjson-3.11.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93de06bc920854552493c81f1f729fab7213b7db4b8195355db5fda02c7d1363", size = 132321, upload-time = "2026-03-31T16:14:32.317Z" }, + { url = "https://files.pythonhosted.org/packages/86/71/089338ee51b3132f050db0864a7df9bdd5e94c2a03820ab8a91e8f655618/orjson-3.11.8-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe0b8c83e0f36247fc9431ce5425a5d95f9b3a689133d494831bdbd6f0bceb13", size = 130658, upload-time = "2026-03-31T16:14:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/10/0d/f39d8802345d0ad65f7fd4374b29b9b59f98656dc30f21ca5c773265b2f0/orjson-3.11.8-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97d823831105c01f6c8029faf297633dbeb30271892bd430e9c24ceae3734744", size = 135708, upload-time = "2026-03-31T16:14:35.224Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b5/40aae576b3473511696dcffea84fde638b2b64774eb4dcb8b2c262729f8a/orjson-3.11.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60c0423f15abb6cf78f56dff00168a1b582f7a1c23f114036e2bfc697814d5f", size = 147047, upload-time = "2026-03-31T16:14:36.489Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f0/778a84458d1fdaa634b2e572e51ce0b354232f580b2327e1f00a8d88c38c/orjson-3.11.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01928d0476b216ad2201823b0a74000440360cef4fed1912d297b8d84718f277", size = 133072, upload-time = "2026-03-31T16:14:37.715Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d3/1bbf2fc3ffcc4b829ade554b574af68cec898c9b5ad6420a923c75a073d3/orjson-3.11.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4a639049c44d36a6d1ae0f4a94b271605c745aee5647fa8ffaabcdc01b69a6", size = 133867, upload-time = "2026-03-31T16:14:39.356Z" }, + { url = "https://files.pythonhosted.org/packages/08/94/6413da22edc99a69a8d0c2e83bf42973b8aa94d83ef52a6d39ac85da00bc/orjson-3.11.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3222adff1e1ff0dce93c16146b93063a7793de6c43d52309ae321234cdaf0f4d", size = 142268, upload-time = "2026-03-31T16:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/4a/5f/aa5dbaa6136d7ba55f5461ac2e885efc6e6349424a428927fd46d68f4396/orjson-3.11.8-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3223665349bbfb68da234acd9846955b1a0808cbe5520ff634bf253a4407009b", size = 424008, upload-time = "2026-03-31T16:14:42.637Z" }, + { url = "https://files.pythonhosted.org/packages/fa/aa/2c1962d108c7fe5e27aa03a354b378caf56d8eafdef15fd83dec081ce45a/orjson-3.11.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:61c9d357a59465736022d5d9ba06687afb7611dfb581a9d2129b77a6fcf78e59", size = 147942, upload-time = "2026-03-31T16:14:44.256Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/65f404f4c47eb1b0b4476f03ec838cac0c4aa933920ff81e5dda4dee14e7/orjson-3.11.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58fb9b17b4472c7b1dcf1a54583629e62e23779b2331052f09a9249edf81675b", size = 136640, upload-time = "2026-03-31T16:14:45.884Z" }, + { url = "https://files.pythonhosted.org/packages/90/5f/7b784aea98bdb125a2f2da7c27d6c2d2f6d943d96ef0278bae596d563f85/orjson-3.11.8-cp310-cp310-win32.whl", hash = "sha256:b43dc2a391981d36c42fa57747a49dae793ef1d2e43898b197925b5534abd10a", size = 132066, upload-time = "2026-03-31T16:14:47.397Z" }, + { url = "https://files.pythonhosted.org/packages/92/ec/2e284af8d6c9478df5ef938917743f61d68f4c70d17f1b6e82f7e3b8dba1/orjson-3.11.8-cp310-cp310-win_amd64.whl", hash = "sha256:c98121237fea2f679480765abd566f7713185897f35c9e6c2add7e3a9900eb61", size = 127609, upload-time = "2026-03-31T16:14:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/67/41/5aa7fa3b0f4dc6b47dcafc3cea909299c37e40e9972feabc8b6a74e2730d/orjson-3.11.8-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:003646067cc48b7fcab2ae0c562491c9b5d2cbd43f1e5f16d98fd118c5522d34", size = 229229, upload-time = "2026-03-31T16:14:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d7/57e7f2458e0a2c41694f39fc830030a13053a84f837a5b73423dca1f0938/orjson-3.11.8-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ed193ce51d77a3830cad399a529cd4ef029968761f43ddc549e1bc62b40d88f8", size = 128871, upload-time = "2026-03-31T16:14:51.888Z" }, + { url = "https://files.pythonhosted.org/packages/53/4a/e0fdb9430983e6c46e0299559275025075568aad5d21dd606faee3703924/orjson-3.11.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30491bc4f862aa15744b9738517454f1e46e56c972a2be87d70d727d5b2a8f8", size = 132104, upload-time = "2026-03-31T16:14:53.142Z" }, + { url = "https://files.pythonhosted.org/packages/08/4a/2025a60ff3f5c8522060cda46612d9b1efa653de66ed2908591d8d82f22d/orjson-3.11.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eda5b8b6be91d3f26efb7dc6e5e68ee805bc5617f65a328587b35255f138bf4", size = 130483, upload-time = "2026-03-31T16:14:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3c/b9cde05bdc7b2385c66014e0620627da638d3d04e4954416ab48c31196c5/orjson-3.11.8-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8db7bfb6fe03581bbab54d7c4124a6dd6a7f4273a38f7267197890f094675f", size = 135481, upload-time = "2026-03-31T16:14:55.901Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f2/a8238e7734de7cb589fed319857a8025d509c89dc52fdcc88f39c6d03d5a/orjson-3.11.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8b5231de76c528a46b57010bbd83fb51e056aa0220a372fd5065e978406f1c", size = 146819, upload-time = "2026-03-31T16:14:57.548Z" }, + { url = "https://files.pythonhosted.org/packages/db/10/dbf1e2a3cafea673b1b4350e371877b759060d6018a998643b7040e5de48/orjson-3.11.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58a4a208a6fbfdb7a7327b8f201c6014f189f721fd55d047cafc4157af1bc62a", size = 132846, upload-time = "2026-03-31T16:14:58.91Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fc/55e667ec9c85694038fcff00573d221b085d50777368ee3d77f38668bf3c/orjson-3.11.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f8952d6d2505c003e8f0224ff7858d341fa4e33fef82b91c4ff0ef070f2393c", size = 133580, upload-time = "2026-03-31T16:15:00.519Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a6/c08c589a9aad0cb46c4831d17de212a2b6901f9d976814321ff8e69e8785/orjson-3.11.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0022bb50f90da04b009ce32c512dc1885910daa7cb10b7b0cba4505b16db82a8", size = 142042, upload-time = "2026-03-31T16:15:01.906Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/2f78ea241d52b717d2efc38878615fe80425bf2beb6e68c984dde257a766/orjson-3.11.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ff51f9d657d1afb6f410cb435792ce4e1fe427aab23d2fcd727a2876e21d4cb6", size = 423845, upload-time = "2026-03-31T16:15:03.703Z" }, + { url = "https://files.pythonhosted.org/packages/70/07/c17dcf05dd8045457538428a983bf1f1127928df5bf328cb24d2b7cddacb/orjson-3.11.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6dbe9a97bdb4d8d9d5367b52a7c32549bba70b2739c58ef74a6964a6d05ae054", size = 147729, upload-time = "2026-03-31T16:15:05.203Z" }, + { url = "https://files.pythonhosted.org/packages/90/6c/0fb6e8a24e682e0958d71711ae6f39110e4b9cd8cab1357e2a89cb8e1951/orjson-3.11.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5c370674ebabe16c6ccac33ff80c62bf8a6e59439f5e9d40c1f5ab8fd2215b7", size = 136425, upload-time = "2026-03-31T16:15:07.052Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/4d3cc3a3d616035beb51b24a09bb872942dc452cf2df0c1d11ab35046d9f/orjson-3.11.8-cp311-cp311-win32.whl", hash = "sha256:0e32f7154299f42ae66f13488963269e5eccb8d588a65bc839ed986919fc9fac", size = 131870, upload-time = "2026-03-31T16:15:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/13/26/9fe70f81d16b702f8c3a775e8731b50ad91d22dacd14c7599b60a0941cd1/orjson-3.11.8-cp311-cp311-win_amd64.whl", hash = "sha256:25e0c672a2e32348d2eb33057b41e754091f2835f87222e4675b796b92264f06", size = 127440, upload-time = "2026-03-31T16:15:09.994Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c6/b038339f4145efd2859c1ca53097a52c0bb9cbdd24f947ebe146da1ad067/orjson-3.11.8-cp311-cp311-win_arm64.whl", hash = "sha256:9185589c1f2a944c17e26c9925dcdbc2df061cc4a145395c57f0c51f9b5dbfcd", size = 127399, upload-time = "2026-03-31T16:15:11.412Z" }, + { url = "https://files.pythonhosted.org/packages/01/f6/8d58b32ab32d9215973a1688aebd098252ee8af1766c0e4e36e7831f0295/orjson-3.11.8-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1cd0b77e77c95758f8e1100139844e99f3ccc87e71e6fc8e1c027e55807c549f", size = 229233, upload-time = "2026-03-31T16:15:12.762Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/2ffe35e71f6b92622e8ea4607bf33ecf7dfb51b3619dcfabfd36cbe2d0a5/orjson-3.11.8-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6a3d159d5ffa0e3961f353c4b036540996bf8b9697ccc38261c0eac1fd3347a6", size = 128772, upload-time = "2026-03-31T16:15:14.237Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/1f8682ae50d5c6897a563cb96bc106da8c9cb5b7b6e81a52e4cc086679b9/orjson-3.11.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76070a76e9c5ae661e2d9848f216980d8d533e0f8143e6ed462807b242e3c5e8", size = 131946, upload-time = "2026-03-31T16:15:15.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/4b/5500f76f0eece84226e0689cb48dcde081104c2fa6e2483d17ca13685ffb/orjson-3.11.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54153d21520a71a4c82a0dbb4523e468941d549d221dc173de0f019678cf3813", size = 130368, upload-time = "2026-03-31T16:15:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/da/4e/58b927e08fbe9840e6c920d9e299b051ea667463b1f39a56e668669f8508/orjson-3.11.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:469ac2125611b7c5741a0b3798cd9e5786cbad6345f9f400c77212be89563bec", size = 135540, upload-time = "2026-03-31T16:15:18.404Z" }, + { url = "https://files.pythonhosted.org/packages/56/7c/ba7cb871cba1bcd5cd02ee34f98d894c6cea96353ad87466e5aef2429c60/orjson-3.11.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14778ffd0f6896aa613951a7fbf4690229aa7a543cb2bfbe9f358e08aafa9546", size = 146877, upload-time = "2026-03-31T16:15:19.833Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/eb9c25fc1386696c6a342cd361c306452c75e0b55e86ad602dd4827a7fd7/orjson-3.11.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea56a955056a6d6c550cf18b3348656a9d9a4f02e2d0c02cabf3c73f1055d506", size = 132837, upload-time = "2026-03-31T16:15:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/37/87/5ddeb7fc1fbd9004aeccab08426f34c81a5b4c25c7061281862b015fce2b/orjson-3.11.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a0f57e59a530d18a142f4d4ba6dfc708dc5fdedce45e98ff06b44930a2a48f", size = 133624, upload-time = "2026-03-31T16:15:22.641Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/90048793db94ee4b2fcec4ac8e5ddb077367637d6650be896b3494b79bb7/orjson-3.11.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b48e274f8824567d74e2158199e269597edf00823a1b12b63d48462bbf5123e", size = 141904, upload-time = "2026-03-31T16:15:24.435Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cf/eb284847487821a5d415e54149a6449ba9bfc5872ce63ab7be41b8ec401c/orjson-3.11.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3f262401086a3960586af06c054609365e98407151f5ea24a62893a40d80dbbb", size = 423742, upload-time = "2026-03-31T16:15:26.155Z" }, + { url = "https://files.pythonhosted.org/packages/44/09/e12423d327071c851c13e76936f144a96adacfc037394dec35ac3fc8d1e8/orjson-3.11.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e8c6218b614badf8e229b697865df4301afa74b791b6c9ade01d19a9953a942", size = 147806, upload-time = "2026-03-31T16:15:27.909Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6d/37c2589ba864e582ffe7611643314785c6afb1f83c701654ef05daa8fcc7/orjson-3.11.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093d489fa039ddade2db541097dbb484999fcc65fc2b0ff9819141e2ab364f25", size = 136485, upload-time = "2026-03-31T16:15:29.749Z" }, + { url = "https://files.pythonhosted.org/packages/be/c9/135194a02ab76b04ed9a10f68624b7ebd238bbe55548878b11ff15a0f352/orjson-3.11.8-cp312-cp312-win32.whl", hash = "sha256:e0950ed1bcb9893f4293fd5c5a7ee10934fbf82c4101c70be360db23ce24b7d2", size = 131966, upload-time = "2026-03-31T16:15:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/9796f8fbe3cf30ce9cb696748dbb535e5c87be4bf4fe2e9ca498ef1fa8cf/orjson-3.11.8-cp312-cp312-win_amd64.whl", hash = "sha256:3cf17c141617b88ced4536b2135c552490f07799f6ad565948ea07bef0dcb9a6", size = 127441, upload-time = "2026-03-31T16:15:33.333Z" }, + { url = "https://files.pythonhosted.org/packages/cc/47/5aaf54524a7a4a0dd09dd778f3fa65dd2108290615b652e23d944152bc8e/orjson-3.11.8-cp312-cp312-win_arm64.whl", hash = "sha256:48854463b0572cc87dac7d981aa72ed8bf6deedc0511853dc76b8bbd5482d36d", size = 127364, upload-time = "2026-03-31T16:15:34.748Z" }, + { url = "https://files.pythonhosted.org/packages/66/7f/95fba509bb2305fab0073558f1e8c3a2ec4b2afe58ed9fcb7d3b8beafe94/orjson-3.11.8-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3f23426851d98478c8970da5991f84784a76682213cd50eb73a1da56b95239dc", size = 229180, upload-time = "2026-03-31T16:15:36.426Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9d/b237215c743ca073697d759b5503abd2cb8a0d7b9c9e21f524bcf176ab66/orjson-3.11.8-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ebaed4cef74a045b83e23537b52ef19a367c7e3f536751e355a2a394f8648559", size = 128754, upload-time = "2026-03-31T16:15:38.049Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/27d65b6d11e63f133781425f132807aef793ed25075fec686fc8e46dd528/orjson-3.11.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97c8f5d3b62380b70c36ffacb2a356b7c6becec86099b177f73851ba095ef623", size = 131877, upload-time = "2026-03-31T16:15:39.484Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cc/faee30cd8f00421999e40ef0eba7332e3a625ce91a58200a2f52c7fef235/orjson-3.11.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:436c4922968a619fb7fef1ccd4b8b3a76c13b67d607073914d675026e911a65c", size = 130361, upload-time = "2026-03-31T16:15:41.274Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/a6c55896197f97b6d4b4e7c7fd77e7235517c34f5d6ad5aadd43c54c6d7c/orjson-3.11.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ab359aff0436d80bfe8a23b46b5fea69f1e18aaf1760a709b4787f1318b317f", size = 135521, upload-time = "2026-03-31T16:15:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7c/ca3a3525aa32ff636ebb1778e77e3587b016ab2edb1b618b36ba96f8f2c0/orjson-3.11.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89b6d0b3a8d81e1929d3ab3d92bbc225688bd80a770c49432543928fe09ac55", size = 146862, upload-time = "2026-03-31T16:15:44.341Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0c/18a9d7f18b5edd37344d1fd5be17e94dc652c67826ab749c6e5948a78112/orjson-3.11.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c009e7a2ca9ad0ed1376ce20dd692146a5d9fe4310848904b6b4fee5c5c137", size = 132847, upload-time = "2026-03-31T16:15:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/23/91/7e722f352ad67ca573cee44de2a58fb810d0f4eb4e33276c6a557979fd8a/orjson-3.11.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b895b781b3e395c067129d8551655642dfe9437273211d5404e87ac752b53", size = 133637, upload-time = "2026-03-31T16:15:48.123Z" }, + { url = "https://files.pythonhosted.org/packages/af/04/32845ce13ac5bd1046ddb02ac9432ba856cc35f6d74dde95864fe0ad5523/orjson-3.11.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88006eda83858a9fdf73985ce3804e885c2befb2f506c9a3723cdeb5a2880e3e", size = 141906, upload-time = "2026-03-31T16:15:49.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/5e/c551387ddf2d7106d9039369862245c85738b828844d13b99ccb8d61fd06/orjson-3.11.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:55120759e61309af7fcf9e961c6f6af3dde5921cdb3ee863ef63fd9db126cae6", size = 423722, upload-time = "2026-03-31T16:15:51.176Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/ecfe62434096f8a794d4976728cb59bcfc4a643977f21c2040545d37eb4c/orjson-3.11.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98bdc6cb889d19bed01de46e67574a2eab61f5cc6b768ed50e8ac68e9d6ffab6", size = 147801, upload-time = "2026-03-31T16:15:52.939Z" }, + { url = "https://files.pythonhosted.org/packages/18/6d/0dce10b9f6643fdc59d99333871a38fa5a769d8e2fc34a18e5d2bfdee900/orjson-3.11.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:708c95f925a43ab9f34625e45dcdadf09ec8a6e7b664a938f2f8d5650f6c090b", size = 136460, upload-time = "2026-03-31T16:15:54.431Z" }, + { url = "https://files.pythonhosted.org/packages/01/d6/6dde4f31842d87099238f1f07b459d24edc1a774d20687187443ab044191/orjson-3.11.8-cp313-cp313-win32.whl", hash = "sha256:01c4e5a6695dc09098f2e6468a251bc4671c50922d4d745aff1a0a33a0cf5b8d", size = 131956, upload-time = "2026-03-31T16:15:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f9/4e494a56e013db957fb77186b818b916d4695b8fa2aa612364974160e91b/orjson-3.11.8-cp313-cp313-win_amd64.whl", hash = "sha256:c154a35dd1330707450bb4d4e7dd1f17fa6f42267a40c1e8a1daa5e13719b4b8", size = 127410, upload-time = "2026-03-31T16:15:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/57/7f/803203d00d6edb6e9e7eef421d4e1adbb5ea973e40b3533f3cfd9aeb374e/orjson-3.11.8-cp313-cp313-win_arm64.whl", hash = "sha256:4861bde57f4d253ab041e374f44023460e60e71efaa121f3c5f0ed457c3a701e", size = 127338, upload-time = "2026-03-31T16:15:59.106Z" }, + { url = "https://files.pythonhosted.org/packages/6d/35/b01910c3d6b85dc882442afe5060cbf719c7d1fc85749294beda23d17873/orjson-3.11.8-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ec795530a73c269a55130498842aaa762e4a939f6ce481a7e986eeaa790e9da4", size = 229171, upload-time = "2026-03-31T16:16:00.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/56/c9ec97bd11240abef39b9e5d99a15462809c45f677420fd148a6c5e6295e/orjson-3.11.8-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c492a0e011c0f9066e9ceaa896fbc5b068c54d365fea5f3444b697ee01bc8625", size = 128746, upload-time = "2026-03-31T16:16:02.673Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/66d4f30a90de45e2f0cbd9623588e8ae71eef7679dbe2ae954ed6d66a41f/orjson-3.11.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:883206d55b1bd5f5679ad5e6ddd3d1a5e3cac5190482927fdb8c78fb699193b5", size = 131867, upload-time = "2026-03-31T16:16:04.342Z" }, + { url = "https://files.pythonhosted.org/packages/19/30/2a645fc9286b928675e43fa2a3a16fb7b6764aa78cc719dc82141e00f30b/orjson-3.11.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5774c1fdcc98b2259800b683b19599c133baeb11d60033e2095fd9d4667b82db", size = 124664, upload-time = "2026-03-31T16:16:05.837Z" }, + { url = "https://files.pythonhosted.org/packages/db/44/77b9a86d84a28d52ba3316d77737f6514e17118119ade3f91b639e859029/orjson-3.11.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7381c83dd3d4a6347e6635950aa448f54e7b8406a27c7ecb4a37e9f1ae08b", size = 129701, upload-time = "2026-03-31T16:16:07.407Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/eff3d9bfe47e9bc6969c9181c58d9f71237f923f9c86a2d2f490cd898c82/orjson-3.11.8-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14439063aebcb92401c11afc68ee4e407258d2752e62d748b6942dad20d2a70d", size = 141202, upload-time = "2026-03-31T16:16:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/90d4b4c60c84d62068d0cf9e4d8f0a4e05e76971d133ac0c60d818d4db20/orjson-3.11.8-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa72e71977bff96567b0f500fc5bfd2fdf915f34052c782a4c6ebbdaa97aa858", size = 127194, upload-time = "2026-03-31T16:16:11.02Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c7/ea9e08d1f0ba981adffb629811148b44774d935171e7b3d780ae43c4c254/orjson-3.11.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7679bc2f01bb0d219758f1a5f87bb7c8a81c0a186824a393b366876b4948e14f", size = 133639, upload-time = "2026-03-31T16:16:13.434Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/ddbbfd6ba59453c8fc7fe1d0e5983895864e264c37481b2a791db635f046/orjson-3.11.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:14f7b8fcb35ef403b42fa5ecfa4ed032332a91f3dc7368fbce4184d59e1eae0d", size = 141914, upload-time = "2026-03-31T16:16:14.955Z" }, + { url = "https://files.pythonhosted.org/packages/4e/31/dbfbefec9df060d34ef4962cd0afcb6fa7a9ec65884cb78f04a7859526c3/orjson-3.11.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c2bdf7b2facc80b5e34f48a2d557727d5c5c57a8a450de122ae81fa26a81c1bc", size = 423800, upload-time = "2026-03-31T16:16:16.594Z" }, + { url = "https://files.pythonhosted.org/packages/87/cf/f74e9ae9803d4ab46b163494adba636c6d7ea955af5cc23b8aaa94cfd528/orjson-3.11.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ccd7ba1b0605813a0715171d39ec4c314cb97a9c85893c2c5c0c3a3729df38bf", size = 147837, upload-time = "2026-03-31T16:16:18.585Z" }, + { url = "https://files.pythonhosted.org/packages/64/e6/9214f017b5db85e84e68602792f742e5dc5249e963503d1b356bee611e01/orjson-3.11.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbc8c9c02463fef4d3c53a9ba3336d05496ec8e1f1c53326a1e4acc11f5c600", size = 136441, upload-time = "2026-03-31T16:16:20.151Z" }, + { url = "https://files.pythonhosted.org/packages/24/dd/3590348818f58f837a75fb969b04cdf187ae197e14d60b5e5a794a38b79d/orjson-3.11.8-cp314-cp314-win32.whl", hash = "sha256:0b57f67710a8cd459e4e54eb96d5f77f3624eba0c661ba19a525807e42eccade", size = 131983, upload-time = "2026-03-31T16:16:21.823Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0f/b6cb692116e05d058f31ceee819c70f097fa9167c82f67fabe7516289abc/orjson-3.11.8-cp314-cp314-win_amd64.whl", hash = "sha256:735e2262363dcbe05c35e3a8869898022af78f89dde9e256924dc02e99fe69ca", size = 127396, upload-time = "2026-03-31T16:16:23.685Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d1/facb5b5051fabb0ef9d26c6544d87ef19a939a9a001198655d0d891062dd/orjson-3.11.8-cp314-cp314-win_arm64.whl", hash = "sha256:6ccdea2c213cf9f3d9490cbd5d427693c870753df41e6cb375bd79bcbafc8817", size = 127330, upload-time = "2026-03-31T16:16:25.496Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "peft" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/cf/037f1e3d5186496c05513a6754639e2dab3038a05f384284d49a9bd06a2d/peft-0.19.1.tar.gz", hash = "sha256:0d97542fe96dcdaa20d3b81c06f26f988618f416a73544ab23c3618ccb674a40", size = 763738, upload-time = "2026-04-16T15:46:45.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/b6/f54d676ed93cc2dd2234c3b172ea9c8c3d7d29361e66b1b23dec57a67465/peft-0.19.1-py3-none-any.whl", hash = "sha256:2113f72a81621b5913ef28f9022204c742df111890c5f49d812716a4a301e356", size = 680692, upload-time = "2026-04-16T15:46:42.886Z" }, +] + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658, upload-time = "2024-07-01T09:45:25.292Z" }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075, upload-time = "2024-07-01T09:45:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808, upload-time = "2024-07-01T09:45:30.305Z" }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290, upload-time = "2024-07-01T09:45:32.868Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163, upload-time = "2024-07-01T09:45:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100, upload-time = "2024-07-01T09:45:37.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880, upload-time = "2024-07-01T09:45:39.89Z" }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218, upload-time = "2024-07-01T09:45:42.771Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487, upload-time = "2024-07-01T09:45:45.176Z" }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219, upload-time = "2024-07-01T09:45:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685, upload-time = "2024-07-01T09:46:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883, upload-time = "2024-07-01T09:46:47.331Z" }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837, upload-time = "2024-07-01T09:46:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562, upload-time = "2024-07-01T09:46:51.811Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761, upload-time = "2024-07-01T09:46:53.961Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767, upload-time = "2024-07-01T09:46:56.664Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989, upload-time = "2024-07-01T09:46:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255, upload-time = "2024-07-01T09:47:01.189Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603, upload-time = "2024-07-01T09:47:03.918Z" }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972, upload-time = "2024-07-01T09:47:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375, upload-time = "2024-07-01T09:47:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889, upload-time = "2024-07-01T09:48:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160, upload-time = "2024-07-01T09:48:07.206Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020, upload-time = "2024-07-01T09:48:09.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539, upload-time = "2024-07-01T09:48:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125, upload-time = "2024-07-01T09:48:14.891Z" }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373, upload-time = "2024-07-01T09:48:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, +] + +[[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.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/bf/a34fee1d624152124fa8355c42f34195ad5fe5233ce5bb87946432047d52/pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb", size = 35076681, upload-time = "2026-04-21T08:51:46.845Z" }, + { url = "https://files.pythonhosted.org/packages/1d/41/64180033d7027afce12dc96d0fe1f504c6fa112190582b458acea2399530/pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147", size = 36684260, upload-time = "2026-04-21T08:51:53.642Z" }, + { url = "https://files.pythonhosted.org/packages/57/02/9b9320e673dd8a99411fac78690f3df92f6dd6f59754c750110bca66d64e/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c", size = 45698566, upload-time = "2026-04-21T10:46:02.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/33/f75e91b9a64c3f33c787e263c93b871ad91b8a4a68c1d5cebddd9840e835/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041", size = 48835562, upload-time = "2026-04-21T10:46:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/a5/63/097510448e47e4091faa41c43ba92f97cecaab8f4535b56a3d149578f634/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491", size = 49394997, upload-time = "2026-04-21T10:46:18.08Z" }, + { url = "https://files.pythonhosted.org/packages/60/6b/c047d6222ab279024a062742d1807e2fbaf27bba88a98637299ff47b9236/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1", size = 51911424, upload-time = "2026-04-21T10:46:25.347Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ba/464cc70761c2a525d97ebd84e21c31ebd47f3ef4bdcee117009f51c46f24/pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591", size = 27251730, upload-time = "2026-04-21T10:46:30.913Z" }, + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +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/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[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-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, +] + +[[package]] +name = "pytz" +version = "2026.1.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, +] + +[[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/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "regex" +version = "2026.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000, upload-time = "2026-04-03T20:56:28.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/59/fd98f8fd54b3feaa76a855324c676c17668c5a1121ec91b7ec96b01bf865/regex-2026.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74fa82dcc8143386c7c0392e18032009d1db715c25f4ba22d23dc2e04d02a20f", size = 489403, upload-time = "2026-04-03T20:52:39.742Z" }, + { url = "https://files.pythonhosted.org/packages/6c/64/d0f222f68e3579d50babf0e4fcc9c9639ef0587fecc00b15e1e46bfc32fa/regex-2026.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a85b620a388d6c9caa12189233109e236b3da3deffe4ff11b84ae84e218a274f", size = 291208, upload-time = "2026-04-03T20:52:42.943Z" }, + { url = "https://files.pythonhosted.org/packages/16/7f/3fab9709b0b0060ba81a04b8a107b34147cd14b9c5551b772154d6505504/regex-2026.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2895506ebe32cc63eeed8f80e6eae453171cfccccab35b70dc3129abec35a5b8", size = 289214, upload-time = "2026-04-03T20:52:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/14/bc/f5dcf04fd462139dcd75495c02eee22032ef741cfa151386a39c3f5fc9b5/regex-2026.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6780f008ee81381c737634e75c24e5a6569cc883c4f8e37a37917ee79efcafd9", size = 785505, upload-time = "2026-04-03T20:52:46.35Z" }, + { url = "https://files.pythonhosted.org/packages/37/36/8a906e216d5b4de7ec3788c1d589b45db40c1c9580cd7b326835cfc976d4/regex-2026.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:88e9b048345c613f253bea4645b2fe7e579782b82cac99b1daad81e29cc2ed8e", size = 852129, upload-time = "2026-04-03T20:52:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/a5/bb/bad2d79be0917a6ef31f5e0f161d9265cb56fd90a3ae1d2e8d991882a48b/regex-2026.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:be061028481186ba62a0f4c5f1cc1e3d5ab8bce70c89236ebe01023883bc903b", size = 899578, upload-time = "2026-04-03T20:52:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b9/7cd0ceb58cd99c70806241636640ae15b4a3fe62e22e9b99afa67a0d7965/regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2228c02b368d69b724c36e96d3d1da721561fb9cc7faa373d7bf65e07d75cb5", size = 793634, upload-time = "2026-04-03T20:52:53Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fb/c58e3ea40ed183806ccbac05c29a3e8c2f88c1d3a66ed27860d5cad7c62d/regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0540e5b733618a2f84e9cb3e812c8afa82e151ca8e19cf6c4e95c5a65198236f", size = 786210, upload-time = "2026-04-03T20:52:54.713Z" }, + { url = "https://files.pythonhosted.org/packages/54/a9/53790fc7a6c948a7be2bc7214fd9cabdd0d1ba561b0f401c91f4ff0357f0/regex-2026.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf9b1b2e692d4877880388934ac746c99552ce6bf40792a767fd42c8c99f136d", size = 769930, upload-time = "2026-04-03T20:52:56.825Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/29ca44729191c79f5476538cd0fa04fa2553b3c45508519ecea4c7afa8f6/regex-2026.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:011bb48bffc1b46553ac704c975b3348717f4e4aa7a67522b51906f99da1820c", size = 774892, upload-time = "2026-04-03T20:52:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/3e/db/6ae74ef8a4cfead341c367e4eed45f71fb1aaba35827a775eed4f1ba4f74/regex-2026.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8512fcdb43f1bf18582698a478b5ab73f9c1667a5b7548761329ef410cd0a760", size = 848816, upload-time = "2026-04-03T20:53:00.684Z" }, + { url = "https://files.pythonhosted.org/packages/53/9a/f7f2c1c6b610d7c6de1c3dc5951effd92c324b1fde761af2044b4721020f/regex-2026.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:867bddc63109a0276f5a31999e4c8e0eb7bbbad7d6166e28d969a2c1afeb97f9", size = 758363, upload-time = "2026-04-03T20:53:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/dd/55/e5386d393bbf8b43c8b084703a46d635e7b2bdc6e0f5909a2619ea1125f1/regex-2026.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1b9a00b83f3a40e09859c78920571dcb83293c8004079653dd22ec14bbfa98c7", size = 837122, upload-time = "2026-04-03T20:53:03.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/cc78710ea2e60b10bacfcc9beb18c67514200ab03597b3b2b319995785c2/regex-2026.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e355be718caf838aa089870259cf1776dc2a4aa980514af9d02c59544d9a8b22", size = 782140, upload-time = "2026-04-03T20:53:05.608Z" }, + { url = "https://files.pythonhosted.org/packages/a2/5f/c7bcba41529105d6c2ca7080ecab7184cd00bee2e1ad1fdea80e618704ea/regex-2026.4.4-cp310-cp310-win32.whl", hash = "sha256:33bfda9684646d323414df7abe5692c61d297dbb0530b28ec66442e768813c59", size = 266225, upload-time = "2026-04-03T20:53:07.342Z" }, + { url = "https://files.pythonhosted.org/packages/eb/26/a745729c2c49354ec4f4bce168f29da932ca01b4758227686cc16c7dde1b/regex-2026.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:0709f22a56798457ae317bcce42aacee33c680068a8f14097430d9f9ba364bee", size = 278393, upload-time = "2026-04-03T20:53:08.65Z" }, + { url = "https://files.pythonhosted.org/packages/87/8b/4327eeb9dbb4b098ebecaf02e9f82b79b6077beeb54c43d9a0660cf7c44c/regex-2026.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:ee9627de8587c1a22201cb16d0296ab92b4df5cdcb5349f4e9744d61db7c7c98", size = 270470, upload-time = "2026-04-03T20:53:10.018Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/617356cbecdb452812a5d42f720d6d5096b360d4a4c1073af700ea140ad2/regex-2026.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4c36a85b00fadb85db9d9e90144af0a980e1a3d2ef9cd0f8a5bef88054657c6", size = 489415, upload-time = "2026-04-03T20:53:11.645Z" }, + { url = "https://files.pythonhosted.org/packages/20/e6/bf057227144d02e3ba758b66649e87531d744dda5f3254f48660f18ae9d8/regex-2026.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5453ecf9cd58b562967badd1edbf092b0588a3af9e32ee3d05c985077ce87", size = 291205, upload-time = "2026-04-03T20:53:13.289Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/637181b787dd1a820ba1c712cee2b4144cd84a32dc776ca067b12b2d70c8/regex-2026.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6aa809ed4dc3706cc38594d67e641601bd2f36d5555b2780ff074edfcb136cf8", size = 289225, upload-time = "2026-04-03T20:53:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/05/21/bac05d806ed02cd4b39d9c8e5b5f9a2998c94c3a351b7792e80671fa5315/regex-2026.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33424f5188a7db12958246a54f59a435b6cb62c5cf9c8d71f7cc49475a5fdada", size = 792434, upload-time = "2026-04-03T20:53:17.414Z" }, + { url = "https://files.pythonhosted.org/packages/d9/17/c65d1d8ae90b772d5758eb4014e1e011bb2db353fc4455432e6cc9100df7/regex-2026.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d346fccdde28abba117cc9edc696b9518c3307fbfcb689e549d9b5979018c6d", size = 861730, upload-time = "2026-04-03T20:53:18.903Z" }, + { url = "https://files.pythonhosted.org/packages/ad/64/933321aa082a2c6ee2785f22776143ba89840189c20d3b6b1d12b6aae16b/regex-2026.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:415a994b536440f5011aa77e50a4274d15da3245e876e5c7f19da349caaedd87", size = 906495, upload-time = "2026-04-03T20:53:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/01/ea/4c8d306e9c36ac22417336b1e02e7b358152c34dc379673f2d331143725f/regex-2026.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21e5eb86179b4c67b5759d452ea7c48eb135cd93308e7a260aa489ed2eb423a4", size = 799810, upload-time = "2026-04-03T20:53:22.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/7605048f00e1379eba89d610c7d644d8f695dc9b26d3b6ecfa3132b872ff/regex-2026.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:312ec9dd1ae7d96abd8c5a36a552b2139931914407d26fba723f9e53c8186f86", size = 774242, upload-time = "2026-04-03T20:53:25.015Z" }, + { url = "https://files.pythonhosted.org/packages/e9/77/283e0d5023fde22cd9e86190d6d9beb21590a452b195ffe00274de470691/regex-2026.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0d2b28aa1354c7cd7f71b7658c4326f7facac106edd7f40eda984424229fd59", size = 781257, upload-time = "2026-04-03T20:53:26.918Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fb/7f3b772be101373c8626ed34c5d727dcbb8abd42a7b1219bc25fd9a3cc04/regex-2026.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:349d7310eddff40429a099c08d995c6d4a4bfaf3ff40bd3b5e5cb5a5a3c7d453", size = 854490, upload-time = "2026-04-03T20:53:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/56547b80f34f4dd2986e1cdd63b1712932f63b6c4ce2f79c50a6cd79d1c2/regex-2026.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e7ab63e9fe45a9ec3417509e18116b367e89c9ceb6219222a3396fa30b147f80", size = 763544, upload-time = "2026-04-03T20:53:30.917Z" }, + { url = "https://files.pythonhosted.org/packages/ac/2f/ce060fdfea8eff34a8997603532e44cdb7d1f35e3bc253612a8707a90538/regex-2026.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fe896e07a5a2462308297e515c0054e9ec2dd18dfdc9427b19900b37dfe6f40b", size = 844442, upload-time = "2026-04-03T20:53:32.463Z" }, + { url = "https://files.pythonhosted.org/packages/e5/44/810cb113096a1dacbe82789fbfab2823f79d19b7f1271acecb7009ba9b88/regex-2026.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb59c65069498dbae3c0ef07bbe224e1eaa079825a437fb47a479f0af11f774f", size = 789162, upload-time = "2026-04-03T20:53:34.039Z" }, + { url = "https://files.pythonhosted.org/packages/20/96/9647dd7f2ecf6d9ce1fb04dfdb66910d094e10d8fe53e9c15096d8aa0bd2/regex-2026.4.4-cp311-cp311-win32.whl", hash = "sha256:2a5d273181b560ef8397c8825f2b9d57013de744da9e8257b8467e5da8599351", size = 266227, upload-time = "2026-04-03T20:53:35.601Z" }, + { url = "https://files.pythonhosted.org/packages/33/80/74e13262460530c3097ff343a17de9a34d040a5dc4de9cf3a8241faab51c/regex-2026.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:9542ccc1e689e752594309444081582f7be2fdb2df75acafea8a075108566735", size = 278399, upload-time = "2026-04-03T20:53:37.021Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/39f19f47f19dcefa3403f09d13562ca1c0fd07ab54db2bc03148f3f6b46a/regex-2026.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:b5f9fb784824a042be3455b53d0b112655686fdb7a91f88f095f3fee1e2a2a54", size = 270473, upload-time = "2026-04-03T20:53:38.633Z" }, + { url = "https://files.pythonhosted.org/packages/e5/28/b972a4d3df61e1d7bcf1b59fdb3cddef22f88b6be43f161bb41ebc0e4081/regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", size = 490434, upload-time = "2026-04-03T20:53:40.219Z" }, + { url = "https://files.pythonhosted.org/packages/84/20/30041446cf6dc3e0eab344fc62770e84c23b6b68a3b657821f9f80cb69b4/regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", size = 292061, upload-time = "2026-04-03T20:53:41.862Z" }, + { url = "https://files.pythonhosted.org/packages/62/c8/3baa06d75c98c46d4cc4262b71fd2edb9062b5665e868bca57859dadf93a/regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", size = 289628, upload-time = "2026-04-03T20:53:43.701Z" }, + { url = "https://files.pythonhosted.org/packages/31/87/3accf55634caad8c0acab23f5135ef7d4a21c39f28c55c816ae012931408/regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", size = 796651, upload-time = "2026-04-03T20:53:45.379Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0c/aaa2c83f34efedbf06f61cb1942c25f6cf1ee3b200f832c4d05f28306c2e/regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", size = 865916, upload-time = "2026-04-03T20:53:47.064Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f6/8c6924c865124643e8f37823eca845dc27ac509b2ee58123685e71cd0279/regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", size = 912287, upload-time = "2026-04-03T20:53:49.422Z" }, + { url = "https://files.pythonhosted.org/packages/11/0e/a9f6f81013e0deaf559b25711623864970fe6a098314e374ccb1540a4152/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", size = 801126, upload-time = "2026-04-03T20:53:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/71/61/3a0cc8af2dc0c8deb48e644dd2521f173f7e6513c6e195aad9aa8dd77ac5/regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", size = 776788, upload-time = "2026-04-03T20:53:52.889Z" }, + { url = "https://files.pythonhosted.org/packages/64/0b/8bb9cbf21ef7dee58e49b0fdb066a7aded146c823202e16494a36777594f/regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", size = 785184, upload-time = "2026-04-03T20:53:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/99/c2/d3e80e8137b25ee06c92627de4e4d98b94830e02b3e6f81f3d2e3f504cf5/regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", size = 859913, upload-time = "2026-04-03T20:53:57.249Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/9d5d876157d969c804622456ef250017ac7a8f83e0e14f903b9e6df5ce95/regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", size = 765732, upload-time = "2026-04-03T20:53:59.428Z" }, + { url = "https://files.pythonhosted.org/packages/82/80/b568935b4421388561c8ed42aff77247285d3ae3bb2a6ca22af63bae805e/regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", size = 852152, upload-time = "2026-04-03T20:54:01.505Z" }, + { url = "https://files.pythonhosted.org/packages/39/29/f0f81217e21cd998245da047405366385d5c6072048038a3d33b37a79dc0/regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", size = 789076, upload-time = "2026-04-03T20:54:03.323Z" }, + { url = "https://files.pythonhosted.org/packages/49/1d/1d957a61976ab9d4e767dd4f9d04b66cc0c41c5e36cf40e2d43688b5ae6f/regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", size = 266700, upload-time = "2026-04-03T20:54:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/bf575d396aeb58ea13b06ef2adf624f65b70fafef6950a80fc3da9cae3bc/regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", size = 277768, upload-time = "2026-04-03T20:54:07.312Z" }, + { url = "https://files.pythonhosted.org/packages/c9/27/049df16ec6a6828ccd72add3c7f54b4df029669bea8e9817df6fff58be90/regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", size = 270568, upload-time = "2026-04-03T20:54:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273, upload-time = "2026-04-03T20:54:11.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954, upload-time = "2026-04-03T20:54:13.412Z" }, + { url = "https://files.pythonhosted.org/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487, upload-time = "2026-04-03T20:54:15.824Z" }, + { url = "https://files.pythonhosted.org/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646, upload-time = "2026-04-03T20:54:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904, upload-time = "2026-04-03T20:54:20.019Z" }, + { url = "https://files.pythonhosted.org/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304, upload-time = "2026-04-03T20:54:22.403Z" }, + { url = "https://files.pythonhosted.org/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126, upload-time = "2026-04-03T20:54:24.308Z" }, + { url = "https://files.pythonhosted.org/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772, upload-time = "2026-04-03T20:54:26.319Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228, upload-time = "2026-04-03T20:54:28.387Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032, upload-time = "2026-04-03T20:54:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714, upload-time = "2026-04-03T20:54:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078, upload-time = "2026-04-03T20:54:34.546Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181, upload-time = "2026-04-03T20:54:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690, upload-time = "2026-04-03T20:54:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733, upload-time = "2026-04-03T20:54:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565, upload-time = "2026-04-03T20:54:41.883Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126, upload-time = "2026-04-03T20:54:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882, upload-time = "2026-04-03T20:54:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334, upload-time = "2026-04-03T20:54:47.051Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691, upload-time = "2026-04-03T20:54:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227, upload-time = "2026-04-03T20:54:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435, upload-time = "2026-04-03T20:54:52.994Z" }, + { url = "https://files.pythonhosted.org/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358, upload-time = "2026-04-03T20:54:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549, upload-time = "2026-04-03T20:54:57.01Z" }, + { url = "https://files.pythonhosted.org/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364, upload-time = "2026-04-03T20:54:58.981Z" }, + { url = "https://files.pythonhosted.org/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221, upload-time = "2026-04-03T20:55:00.88Z" }, + { url = "https://files.pythonhosted.org/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530, upload-time = "2026-04-03T20:55:03.213Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989, upload-time = "2026-04-03T20:55:05.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241, upload-time = "2026-04-03T20:55:07.162Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921, upload-time = "2026-04-03T20:55:09.62Z" }, + { url = "https://files.pythonhosted.org/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240, upload-time = "2026-04-03T20:55:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440, upload-time = "2026-04-03T20:55:13.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f5/ed97c2dc47b5fbd4b73c0d7d75f9ebc8eca139f2bbef476bba35f28c0a77/regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", size = 490343, upload-time = "2026-04-03T20:55:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/de4828a7385ec166d673a5790ad06ac48cdaa98bc0960108dd4b9cc1aef7/regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", size = 291909, upload-time = "2026-04-03T20:55:17.558Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d6/5cfbfc97f3201a4d24b596a77957e092030dcc4205894bc035cedcfce62f/regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", size = 289692, upload-time = "2026-04-03T20:55:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/f2212d9fd56fe897e36d0110ba30ba2d247bd6410c5bd98499c7e5a1e1f2/regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", size = 796979, upload-time = "2026-04-03T20:55:22.56Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e3/a016c12675fbac988a60c7e1c16e67823ff0bc016beb27bd7a001dbdabc6/regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", size = 866744, upload-time = "2026-04-03T20:55:24.646Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/0b90ca4cf17adc3cb43de80ec71018c37c88ad64987e8d0d481a95ca60b5/regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", size = 911613, upload-time = "2026-04-03T20:55:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3b/2b3dac0b82d41ab43aa87c6ecde63d71189d03fe8854b8ca455a315edac3/regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", size = 800551, upload-time = "2026-04-03T20:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/25/fe/5365eb7aa0e753c4b5957815c321519ecab033c279c60e1b1ae2367fa810/regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", size = 776911, upload-time = "2026-04-03T20:55:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b3/7fb0072156bba065e3b778a7bc7b0a6328212be5dd6a86fd207e0c4f2dab/regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", size = 785751, upload-time = "2026-04-03T20:55:33.797Z" }, + { url = "https://files.pythonhosted.org/packages/02/1a/9f83677eb699273e56e858f7bd95acdbee376d42f59e8bfca2fd80d79df3/regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", size = 860484, upload-time = "2026-04-03T20:55:35.745Z" }, + { url = "https://files.pythonhosted.org/packages/3b/7a/93937507b61cfcff8b4c5857f1b452852b09f741daa9acae15c971d8554e/regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", size = 765939, upload-time = "2026-04-03T20:55:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/86/ea/81a7f968a351c6552b1670ead861e2a385be730ee28402233020c67f9e0f/regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", size = 851417, upload-time = "2026-04-03T20:55:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7e/323c18ce4b5b8f44517a36342961a0306e931e499febbd876bb149d900f0/regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", size = 789056, upload-time = "2026-04-03T20:55:42.303Z" }, + { url = "https://files.pythonhosted.org/packages/c0/af/e7510f9b11b1913b0cd44eddb784b2d650b2af6515bfce4cffcc5bfd1d38/regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", size = 272130, upload-time = "2026-04-03T20:55:44.995Z" }, + { url = "https://files.pythonhosted.org/packages/9a/51/57dae534c915e2d3a21490e88836fa2ae79dde3b66255ecc0c0a155d2c10/regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", size = 280992, upload-time = "2026-04-03T20:55:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5e/abaf9f4c3792e34edb1434f06717fae2b07888d85cb5cec29f9204931bf8/regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", size = 273563, upload-time = "2026-04-03T20:55:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/ff/06/35da85f9f217b9538b99cbb170738993bcc3b23784322decb77619f11502/regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", size = 494191, upload-time = "2026-04-03T20:55:51.258Z" }, + { url = "https://files.pythonhosted.org/packages/54/5b/1bc35f479eef8285c4baf88d8c002023efdeebb7b44a8735b36195486ae7/regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", size = 293877, upload-time = "2026-04-03T20:55:53.214Z" }, + { url = "https://files.pythonhosted.org/packages/39/5b/f53b9ad17480b3ddd14c90da04bfb55ac6894b129e5dea87bcaf7d00e336/regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", size = 292410, upload-time = "2026-04-03T20:55:55.736Z" }, + { url = "https://files.pythonhosted.org/packages/bb/56/52377f59f60a7c51aa4161eecf0b6032c20b461805aca051250da435ffc9/regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", size = 811831, upload-time = "2026-04-03T20:55:57.802Z" }, + { url = "https://files.pythonhosted.org/packages/dd/63/8026310bf066f702a9c361f83a8c9658f3fe4edb349f9c1e5d5273b7c40c/regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", size = 871199, upload-time = "2026-04-03T20:56:00.333Z" }, + { url = "https://files.pythonhosted.org/packages/20/9f/a514bbb00a466dbb506d43f187a04047f7be1505f10a9a15615ead5080ee/regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", size = 917649, upload-time = "2026-04-03T20:56:02.445Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6b/8399f68dd41a2030218839b9b18360d79b86d22b9fab5ef477c7f23ca67c/regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", size = 816388, upload-time = "2026-04-03T20:56:04.595Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9c/103963f47c24339a483b05edd568594c2be486188f688c0170fd504b2948/regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", size = 785746, upload-time = "2026-04-03T20:56:07.13Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ee/7f6054c0dec0cee3463c304405e4ff42e27cff05bf36fcb34be549ab17bd/regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", size = 801483, upload-time = "2026-04-03T20:56:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/51d3d941cf6070dc00c3338ecf138615fc3cce0421c3df6abe97a08af61a/regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", size = 866331, upload-time = "2026-04-03T20:56:12.039Z" }, + { url = "https://files.pythonhosted.org/packages/16/e8/76d50dcc122ac33927d939f350eebcfe3dbcbda96913e03433fc36de5e63/regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", size = 772673, upload-time = "2026-04-03T20:56:14.558Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/5f6bf75e20ea6873d05ba4ec78378c375cbe08cdec571c83fbb01606e563/regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", size = 857146, upload-time = "2026-04-03T20:56:16.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/33/3c76d9962949e487ebba353a18e89399f292287204ac8f2f4cfc3a51c233/regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", size = 803463, upload-time = "2026-04-03T20:56:18.923Z" }, + { url = "https://files.pythonhosted.org/packages/19/eb/ef32dcd2cb69b69bc0c3e55205bce94a7def48d495358946bc42186dcccc/regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", size = 275709, upload-time = "2026-04-03T20:56:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/a0/86/c291bf740945acbf35ed7dbebf8e2eea2f3f78041f6bd7cdab80cb274dc0/regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", size = 285622, upload-time = "2026-04-03T20:56:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/ec846d560ae6a597115153c02ca6138a7877a1748b2072d9521c10a93e58/regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", size = 275773, upload-time = "2026-04-03T20:56:26.07Z" }, +] + +[[package]] +name = "requests" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +] + +[[package]] +name = "safehttpx" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" }, +] + +[[package]] +name = "safetensors" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, + { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, + { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, + { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, + { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6a/4d08d89a6fcbe905c5ae68b8b34f0791850882fc19782d0d02c65abbdf3b/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737", size = 492430, upload-time = "2025-11-19T15:18:11.884Z" }, + { url = "https://files.pythonhosted.org/packages/dd/29/59ed8152b30f72c42d00d241e58eaca558ae9dbfa5695206e2e0f54c7063/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd", size = 503977, upload-time = "2025-11-19T15:18:17.523Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4811bfec67fa260e791369b16dab105e4bae82686120554cc484064e22b4/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2", size = 623890, upload-time = "2025-11-19T15:18:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/58/5b/632a58724221ef03d78ab65062e82a1010e1bef8e8e0b9d7c6d7b8044841/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3", size = 531885, upload-time = "2025-11-19T15:18:27.146Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + +[[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 = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "stable-baselines3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "gymnasium" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/07/01a2aa6a6a58f911085981dad1af2f1877d04886768c0df33930945fb98c/stable_baselines3-2.8.0.tar.gz", hash = "sha256:fe976d102b596c8001ca619638901721bcf97e8934a397e235b2d277fa9216c2", size = 220224, upload-time = "2026-04-01T10:49:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/1b/8046baf1e7756006eab991cd3260c4342c515dd6b536bc2d03df4b6f35aa/stable_baselines3-2.8.0-py3-none-any.whl", hash = "sha256:8c19d960b534a909f46dac5227662fc2d6be380e5c66cb04e1ad23edb23dc5a2", size = 187458, upload-time = "2026-04-01T10:49:28.132Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +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/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/9b/42f93f459cf03062c8b3aab812475f01456fd42e04b08bad69bcaedd15c8/tomlkit-0.12.0.tar.gz", hash = "sha256:01f0477981119c7d8ee0f67ebe0297a7c95b14cf9f4b102b45486deb77018716", size = 190497, upload-time = "2023-07-27T07:49:05.797Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/4f/12207897848a653d03ebbf6775a29d949408ded5f99b2d87198bc5c93508/tomlkit-0.12.0-py3-none-any.whl", hash = "sha256:926f1f37a1587c7a4f6c7484dae538f1345d96d793d9adab5d3675957b1d0766", size = 37334, upload-time = "2023-07-27T07:49:04.789Z" }, +] + +[[package]] +name = "torch" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f2/c1690994afe461aae2d0cac62251e6802a703dec0a6c549c02ecd0de92a9/torch-2.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c0d7fcfbc0c4e8bb5ebc3907cbc0c6a0da1b8f82b1fc6e14e914fa0b9baf74e", size = 80526521, upload-time = "2026-03-23T18:12:06.86Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f0/98ae802fa8c09d3149b0c8690741f3f5753c90e779bd28c9613257295945/torch-2.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4cf8687f4aec3900f748d553483ef40e0ac38411c3c48d0a86a438f6d7a99b18", size = 419723025, upload-time = "2026-03-23T18:11:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/18a9b10b4bd34f12d4e561c52b0ae7158707b8193c6cfc0aad2b48167090/torch-2.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1b32ceda909818a03b112006709b02be1877240c31750a8d9c6b7bf5f2d8a6e5", size = 530589207, upload-time = "2026-03-23T18:11:23.756Z" }, + { url = "https://files.pythonhosted.org/packages/35/40/2d532e8c0e23705be9d1debce5bc37b68d59a39bda7584c26fe9668076fe/torch-2.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3c712ae6fb8e7a949051a953fc412fe0a6940337336c3b6f905e905dac5157f", size = 114518313, upload-time = "2026-03-23T18:11:58.281Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "transformers" +version = "5.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/e9/c6c80a07690142a7d05444271f47b9f3c8aac7dea01d52e1137ee480ad78/transformers-5.6.2.tar.gz", hash = "sha256:e657134c3e5a6bc00a3c35f4e2674bb51adfcd89898495b788a18552bac2b91a", size = 8311867, upload-time = "2026-04-23T18:33:29.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/95/0b0218149b0d6f14df35f5b8f676fa83df4f19ed253c3cc447107ef86eca/transformers-5.6.2-py3-none-any.whl", hash = "sha256:f8d3a1bb96778fed9b8aabfd0dd6e19843e4b0f2bb6b59f32b8a92051b0f348f", size = 10364898, upload-time = "2026-04-23T18:33:26.081Z" }, +] + +[[package]] +name = "triton" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/ba/b1b04f4b291a3205d95ebd24465de0e5bf010a2df27a4e58a9b5f039d8f2/triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781", size = 175972180, upload-time = "2026-01-20T16:15:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, +] + +[[package]] +name = "trl" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "datasets" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/2d3d876917d43537afea7b502abb318ae071295e4accac222741b548399e/trl-1.2.0.tar.gz", hash = "sha256:f5038b21295d2559992a087ea8d9ca10f74cde23e6760861def209811ab45d00", size = 583112, upload-time = "2026-04-17T01:04:17.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/82/bad1a22ff4b21d8080f7a64d8313c1ac0e791b455b98f2efca1ef3e14b8f/trl-1.2.0-py3-none-any.whl", hash = "sha256:f6ddfa162ac92d25973070d9e3f6cff71b32c52edc34539e4294722f9dc0a6d6", size = 697449, upload-time = "2026-04-17T01:04:16.007Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.42.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" }, + { url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" }, + { url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" }, + { url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" }, + { url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" }, + { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, + { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, +] + +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0d/9cc638702f6fc3c7a3685bcc8cf2a9ed7d6206e932a49f5242658047ef51/yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107", size = 123764, upload-time = "2026-03-01T22:04:09.7Z" }, + { url = "https://files.pythonhosted.org/packages/7a/35/5a553687c5793df5429cd1db45909d4f3af7eee90014888c208d086a44f0/yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d", size = 86282, upload-time = "2026-03-01T22:04:11.892Z" }, + { url = "https://files.pythonhosted.org/packages/68/2e/c5a2234238f8ce37a8312b52801ee74117f576b1539eec8404a480434acc/yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05", size = 86053, upload-time = "2026-03-01T22:04:13.292Z" }, + { url = "https://files.pythonhosted.org/packages/74/3f/bbd8ff36fb038622797ffbaf7db314918bb4d76f1cc8a4f9ca7a55fe5195/yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d", size = 99395, upload-time = "2026-03-01T22:04:15.133Z" }, + { url = "https://files.pythonhosted.org/packages/77/04/9516bc4e269d2a3ec9c6779fcdeac51ce5b3a9b0156f06ac7152e5bba864/yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748", size = 92143, upload-time = "2026-03-01T22:04:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/c7/63/88802d1f6b1cb1fc67d67a58cd0cf8a1790de4ce7946e434240f1d60ab4a/yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764", size = 107643, upload-time = "2026-03-01T22:04:18.519Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4f9b838f4d8bdd6f0f385aed8bbf21c71ed11a0b9983305c302cbd557815/yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007", size = 108700, upload-time = "2026-03-01T22:04:20.373Z" }, + { url = "https://files.pythonhosted.org/packages/50/12/95a1d33f04a79c402664070d43b8b9f72dc18914e135b345b611b0b1f8cc/yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4", size = 102769, upload-time = "2026-03-01T22:04:23.055Z" }, + { url = "https://files.pythonhosted.org/packages/86/65/91a0285f51321369fd1a8308aa19207520c5f0587772cfc2e03fc2467e90/yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26", size = 101114, upload-time = "2026-03-01T22:04:25.031Z" }, + { url = "https://files.pythonhosted.org/packages/58/80/c7c8244fc3e5bc483dc71a09560f43b619fab29301a0f0a8f936e42865c7/yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769", size = 98883, upload-time = "2026-03-01T22:04:27.281Z" }, + { url = "https://files.pythonhosted.org/packages/86/e7/71ca9cc9ca79c0b7d491216177d1aed559d632947b8ffb0ee60f7d8b23e3/yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716", size = 94172, upload-time = "2026-03-01T22:04:28.554Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3f/6c6c8a0fe29c26fb2db2e8d32195bb84ec1bfb8f1d32e7f73b787fcf349b/yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993", size = 107010, upload-time = "2026-03-01T22:04:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/56/38/12730c05e5ad40a76374d440ed8b0899729a96c250516d91c620a6e38fc2/yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0", size = 100285, upload-time = "2026-03-01T22:04:31.752Z" }, + { url = "https://files.pythonhosted.org/packages/34/92/6a7be9239f2347234e027284e7a5f74b1140cc86575e7b469d13fba1ebfe/yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750", size = 108230, upload-time = "2026-03-01T22:04:33.844Z" }, + { url = "https://files.pythonhosted.org/packages/5e/81/4aebccfa9376bd98b9d8bfad20621a57d3e8cfc5b8631c1fa5f62cdd03f4/yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6", size = 103008, upload-time = "2026-03-01T22:04:35.856Z" }, + { url = "https://files.pythonhosted.org/packages/38/0f/0b4e3edcec794a86b853b0c6396c0a888d72dfce19b2d88c02ac289fb6c1/yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d", size = 83073, upload-time = "2026-03-01T22:04:38.268Z" }, + { url = "https://files.pythonhosted.org/packages/a0/71/ad95c33da18897e4c636528bbc24a1dd23fe16797de8bc4ec667b8db0ba4/yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb", size = 87328, upload-time = "2026-03-01T22:04:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/dfa369523c79bccf9c9c746b0a63eb31f65db9418ac01275f7950962e504/yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220", size = 82463, upload-time = "2026-03-01T22:04:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/a2/aa/60da938b8f0997ba3a911263c40d82b6f645a67902a490b46f3355e10fae/yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99", size = 123641, upload-time = "2026-03-01T22:04:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c", size = 86248, upload-time = "2026-03-01T22:04:44.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432", size = 85988, upload-time = "2026-03-01T22:04:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6c/4a90d59c572e46b270ca132aca66954f1175abd691f74c1ef4c6711828e2/yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a", size = 100566, upload-time = "2026-03-01T22:04:47.639Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/c438fb5108047e629f6282a371e6e91cf3f97ee087c4fb748a1f32ceef55/yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05", size = 92079, upload-time = "2026-03-01T22:04:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/d9/13/d269aa1aed3e4f50a5a103f96327210cc5fa5dd2d50882778f13c7a14606/yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83", size = 108741, upload-time = "2026-03-01T22:04:50.838Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/115b16f22c37ea4437d323e472945bea97301c8ec6089868fa560abab590/yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c", size = 108099, upload-time = "2026-03-01T22:04:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598", size = 102678, upload-time = "2026-03-01T22:04:55.176Z" }, + { url = "https://files.pythonhosted.org/packages/85/59/cd98e556fbb2bf8fab29c1a722f67ad45c5f3447cac798ab85620d1e70af/yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b", size = 100803, upload-time = "2026-03-01T22:04:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/b39770b56d4a9f0bb5f77e2f1763cd2d75cc2f6c0131e3b4c360348fcd65/yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c", size = 100163, upload-time = "2026-03-01T22:04:58.492Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/6980f99ab00e1f0ff67cb84766c93d595b067eed07439cfccfc8fb28c1a6/yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788", size = 93859, upload-time = "2026-03-01T22:05:00.268Z" }, + { url = "https://files.pythonhosted.org/packages/38/69/912e6c5e146793e5d4b5fe39ff5b00f4d22463dfd5a162bec565ac757673/yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222", size = 108202, upload-time = "2026-03-01T22:05:02.273Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/35ca6767524687ad64e5f5c31ad54bc76d585585a9fcb40f649e7e82ffed/yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb", size = 99866, upload-time = "2026-03-01T22:05:03.597Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1c/1a3387ee6d73589f6f2a220ae06f2984f6c20b40c734989b0a44f5987308/yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc", size = 107852, upload-time = "2026-03-01T22:05:04.986Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/35c0750fcd5a3f781058bfd954515dd4b1eab45e218cbb85cf11132215f1/yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2", size = 102919, upload-time = "2026-03-01T22:05:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/9a1979aec4a81896d597bcb2177827f2dbee3f5b7cc48b2d0dadb644b41d/yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5", size = 82602, upload-time = "2026-03-01T22:05:08.444Z" }, + { url = "https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46", size = 87461, upload-time = "2026-03-01T22:05:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/93/95/07e3553fe6f113e6864a20bdc53a78113cda3b9ced8784ee52a52c9f80d8/yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928", size = 82336, upload-time = "2026-03-01T22:05:11.554Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +] diff --git a/visualize.py b/visualize.py new file mode 100644 index 0000000000000000000000000000000000000000..3b3a6be2e3f410888e0ddb8d0f02b1592a1fcc6d --- /dev/null +++ b/visualize.py @@ -0,0 +1,203 @@ +""" +Episode visualization for Budget Router environment. + +Generates a 4-panel matplotlib figure showing: + 1. Provider health degradation over time + 2. Budget remaining curve + 3. Action distribution per step (color-coded strip) + 4. Cumulative reward trajectory + +Usage: + python visualize.py --scenario hard_multi --seed 42 + python visualize.py --scenario medium --policy oracle +""" + +from __future__ import annotations + +import argparse +import random +from typing import Any, Dict, List + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +from budget_router.environment import BudgetRouterEnv +from budget_router.models import Action, ActionType +from budget_router.policies import ( + debug_upper_bound_policy, + heuristic_baseline_policy, + random_policy, +) +from budget_router.tasks import TASK_PRESETS + +ACTION_ORDER = ["route_to_a", "route_to_b", "route_to_c", "shed_load"] +ACTION_LABELS = ["Route A", "Route B", "Route C", "Shed Load"] +ACTION_COLORS = ["#e74c3c", "#3498db", "#2ecc71", "#95a5a6"] + + +def run_and_trace( + env: BudgetRouterEnv, + policy_fn: Any, + seed: int, + scenario_name: str, + policy_name: str = "heuristic_baseline", +) -> Dict[str, List]: + """Run an episode and collect per-step trace data for visualization.""" + scenario = TASK_PRESETS[scenario_name] + obs = env.reset(seed=seed, scenario=scenario) + rng = random.Random(seed + 10000) if "random" in policy_name else None + + trace: Dict[str, List] = { + "step": [], + "a_health": [], + "b_health": [], + "c_health": [], + "budget": [], + "budget_pct": [], + "reward": [], + "cumulative_reward": [], + "action": [], + "latency_ms": [], + "queue_backlog": [], + } + + cumulative = 0.0 + steps = 0 + initial_budget = scenario.initial_budget + + while not obs.done and steps < scenario.max_steps: + if "upper_bound" in policy_name: + action = policy_fn(obs, env._internal) + elif "random" in policy_name: + action = policy_fn(obs, rng=rng) + else: + action = policy_fn(obs) + + obs = env.step(action) + steps += 1 + + reward = obs.reward or 0.0 + cumulative += reward + s = env._internal + + trace["step"].append(steps) + trace["a_health"].append(s.providers["A"].current_health) + trace["b_health"].append(s.providers["B"].current_health) + trace["c_health"].append(s.providers["C"].current_health) + trace["budget"].append(s.budget_dollars) + trace["budget_pct"].append(s.budget_dollars / initial_budget if initial_budget > 0 else 0) + trace["reward"].append(reward) + trace["cumulative_reward"].append(cumulative) + trace["action"].append(action.action_type.value) + trace["latency_ms"].append(s.last_latency_ms) + trace["queue_backlog"].append(s.queue_backlog_count) + + return trace + + +def render_episode(trace: Dict[str, List], scenario_name: str, policy_name: str, seed: int) -> plt.Figure: + """Create a 4-panel matplotlib figure from episode trace data.""" + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle( + f"Budget Router Episode — {scenario_name.upper()} / {policy_name.replace('_', ' ').title()} / seed={seed}", + fontsize=14, + fontweight="bold", + ) + + steps = trace["step"] + + # ── Panel 1: Provider health degradation ── + ax1 = axes[0, 0] + ax1.plot(steps, trace["a_health"], "o-", color="#e74c3c", label="Provider A", linewidth=2, markersize=4) + ax1.plot(steps, trace["b_health"], "s-", color="#3498db", label="Provider B", linewidth=2, markersize=4) + ax1.plot(steps, trace["c_health"], "^-", color="#2ecc71", label="Provider C", linewidth=2, markersize=4) + ax1.axhline(y=0.52, color="gray", linestyle="--", alpha=0.5, label="Heuristic threshold (0.52)") + ax1.set_xlabel("Step") + ax1.set_ylabel("Health (true)") + ax1.set_title("Provider Health Degradation") + ax1.legend(fontsize=8, loc="upper right") + ax1.set_ylim(-0.05, 1.05) + ax1.grid(True, alpha=0.3) + + # ── Panel 2: Budget remaining ── + ax2 = axes[0, 1] + ax2.plot(steps, trace["budget"], "o-", color="#f39c12", linewidth=2, markersize=4) + ax2.fill_between(steps, trace["budget"], alpha=0.2, color="#f39c12") + ax2.axhline(y=0, color="red", linestyle="--", alpha=0.7, label="Budget exhausted") + ax2.set_xlabel("Step") + ax2.set_ylabel("Budget Remaining ($)") + ax2.set_title("Budget Remaining") + ax2.legend(fontsize=8) + ax2.grid(True, alpha=0.3) + + # ── Panel 3: Action distribution (color-coded strip) ── + ax3 = axes[1, 0] + action_map = {name: i for i, name in enumerate(ACTION_ORDER)} + for i, (action_val, step_val) in enumerate(zip(trace["action"], steps)): + idx = action_map.get(action_val, 3) + ax3.barh(idx, 0.8, left=step_val - 0.4, height=0.6, color=ACTION_COLORS[idx], edgecolor="white", linewidth=0.5) + + ax3.set_yticks(range(len(ACTION_LABELS))) + ax3.set_yticklabels(ACTION_LABELS) + ax3.set_xlabel("Step") + ax3.set_title("Action per Step") + ax3.set_xlim(0.5, max(steps) + 0.5 if steps else 20.5) + ax3.grid(True, alpha=0.3, axis="x") + + # Add legend patches manually + from matplotlib.patches import Patch + legend_patches = [Patch(facecolor=ACTION_COLORS[i], label=ACTION_LABELS[i]) for i in range(len(ACTION_LABELS))] + ax3.legend(handles=legend_patches, fontsize=7, loc="upper right", ncol=2) + + # ── Panel 4: Cumulative reward ── + ax4 = axes[1, 1] + ax4.plot(steps, trace["cumulative_reward"], "o-", color="#9b59b6", linewidth=2, markersize=4) + ax4.fill_between(steps, trace["cumulative_reward"], alpha=0.1, color="#9b59b6") + ax4.axhline(y=0, color="gray", linestyle="--", alpha=0.5) + ax4.set_xlabel("Step") + ax4.set_ylabel("Cumulative Reward") + ax4.set_title(f"Cumulative Reward (total: {trace['cumulative_reward'][-1]:.2f})") + ax4.grid(True, alpha=0.3) + + plt.tight_layout() + return fig + + +def main() -> None: + parser = argparse.ArgumentParser(description="Visualize Budget Router episode") + parser.add_argument("--scenario", default="hard_multi", choices=list(TASK_PRESETS.keys())) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument( + "--policy", + default="heuristic_baseline", + choices=["heuristic_baseline", "oracle", "random"], + ) + parser.add_argument("--output", default=None, help="Output file path (default: docs/{scenario}_{policy}.png)") + args = parser.parse_args() + + policies = { + "heuristic_baseline": (heuristic_baseline_policy, "heuristic_baseline"), + "oracle": (debug_upper_bound_policy, "upper_bound"), + "random": (random_policy, "random"), + } + + policy_fn, policy_name = policies[args.policy] + env = BudgetRouterEnv() + + print(f"Running episode: {args.scenario} / {args.policy} / seed={args.seed}") + trace = run_and_trace(env, policy_fn, args.seed, args.scenario, policy_name=policy_name) + print(f"Episode complete: {len(trace['step'])} steps, total reward={trace['cumulative_reward'][-1]:.2f}") + + fig = render_episode(trace, args.scenario, args.policy, args.seed) + + output = args.output or f"docs/{args.scenario}_{args.policy}_seed{args.seed}.png" + fig.savefig(output, dpi=150, bbox_inches="tight") + print(f"Saved to: {output}") + plt.close(fig) + + +if __name__ == "__main__": + main()