diff --git a/.cherry_picker.toml b/.cherry_picker.toml new file mode 100644 index 000000000..3d35a58a2 --- /dev/null +++ b/.cherry_picker.toml @@ -0,0 +1,5 @@ +team = "lightspeed-core" +repo = "lightspeed-stack" +check_sha = "ea5de69d94e33aa2a75a622fec455bc4b0fa6c6b" +fix_commit_msg = false +default_branch = "main" diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json new file mode 100644 index 000000000..e0ff11136 --- /dev/null +++ b/.github/pr-title-checker-config.json @@ -0,0 +1,23 @@ +{ + "LABEL": { + "name": "title needs formatting", + "color": "EEEEEE" + }, + "CHECKS": { + "prefixes": [ + "LCORE-", + "RSPEED-", + "MGTM-", + "OLS-", + "RHIDP-", + "LEADS-", + "CWFHEALTH-", + "[release/" + ] + }, + "MESSAGES": { + "success": "All OK", + "failure": "Failing CI test", + "notice": "" + } +} diff --git a/.github/workflows/build_and_push_release.yaml b/.github/workflows/build_and_push_release.yaml index 9c74f4b22..e43ba13db 100644 --- a/.github/workflows/build_and_push_release.yaml +++ b/.github/workflows/build_and_push_release.yaml @@ -27,15 +27,23 @@ jobs: # qemu is required for arm64 builds sudo apt install -y buildah qemu-user-static - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 + with: + # Fetch submodules (required for lightspeed-providers) + submodules: 'recursive' + # Fetch all tags to determine latest stable version + fetch-tags: true + - name: Determine if latest tag should be applied + id: check_latest + run: ./scripts/latest-tag.py - name: Build image with Buildah id: build_image uses: redhat-actions/buildah-build@v2 with: image: ${{ env.IMAGE_NAME }} - tags: | + tags: >- ${{ env.GIT_TAG }} - ${{ env.LATEST_TAG }} + ${{ steps.check_latest.outputs.apply_latest == 'true' && env.LATEST_TAG || '' }} containerfiles: | ${{ env.CONTAINER_FILE }} archs: amd64, arm64 @@ -48,7 +56,7 @@ jobs: - name: Check manifest run: | set -x - buildah manifest inspect ${{ steps.build_image.outputs.image }}:${{ env.LATEST_TAG }} + buildah manifest inspect ${{ steps.build_image.outputs.image-with-tag }} - name: Push image to Quay.io uses: redhat-actions/push-to-registry@v2 with: diff --git a/.github/workflows/e2e_tests.yaml b/.github/workflows/e2e_tests.yaml index 228a71946..ec7095630 100644 --- a/.github/workflows/e2e_tests.yaml +++ b/.github/workflows/e2e_tests.yaml @@ -21,7 +21,7 @@ jobs: FAISS_VECTOR_STORE_ID: ${{ vars.FAISS_VECTOR_STORE_ID }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: # On PR_TARGET → the fork (or same repo) that opened the PR. # On push → falls back to the current repository. @@ -31,6 +31,11 @@ jobs: # On push → the pushed commit that triggered the workflow. ref: ${{ github.event.pull_request.head.ref || github.sha }} + # Tests need access to secrets. + # This should be refactored if possible to mitigate the risk. + # https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target + allow-unsafe-pr-checkout: true + # Don’t keep credentials when running untrusted PR code under PR_TARGET. persist-credentials: ${{ github.event_name != 'pull_request_target' }} diff --git a/.github/workflows/e2e_tests_providers.yaml b/.github/workflows/e2e_tests_providers.yaml index 82886d6a2..9bb82f8b2 100644 --- a/.github/workflows/e2e_tests_providers.yaml +++ b/.github/workflows/e2e_tests_providers.yaml @@ -27,7 +27,7 @@ jobs: FAISS_VECTOR_STORE_ID: ${{ vars.FAISS_VECTOR_STORE_ID }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: # On PR_TARGET → the fork (or same repo) that opened the PR. # On push → falls back to the current repository. @@ -37,6 +37,11 @@ jobs: # On push → the pushed commit that triggered the workflow. ref: ${{ github.event.pull_request.head.ref || github.sha }} + # Tests need access to secrets. + # This should be refactored if possible to mitigate the risk. + # https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target + allow-unsafe-pr-checkout: true + # Don’t keep credentials when running untrusted PR code under PR_TARGET. persist-credentials: ${{ github.event_name != 'pull_request_target' }} diff --git a/.github/workflows/e2e_tests_rhaiis.yaml b/.github/workflows/e2e_tests_rhaiis.yaml index 54a0080e2..347c5b968 100644 --- a/.github/workflows/e2e_tests_rhaiis.yaml +++ b/.github/workflows/e2e_tests_rhaiis.yaml @@ -28,7 +28,7 @@ jobs: FAISS_VECTOR_STORE_ID: ${{ vars.FAISS_VECTOR_STORE_ID }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: # On PR_TARGET → the fork (or same repo) that opened the PR. # On push → falls back to the current repository. @@ -38,6 +38,11 @@ jobs: # On push → the pushed commit that triggered the workflow. ref: ${{ github.event.pull_request.head.ref || github.sha }} + # Tests need access to secrets. + # This should be refactored if possible to mitigate the risk. + # https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target + allow-unsafe-pr-checkout: true + # Don’t keep credentials when running untrusted PR code under PR_TARGET. persist-credentials: ${{ github.event_name != 'pull_request_target' }} diff --git a/.github/workflows/radon.yaml b/.github/workflows/radon.yaml index f21e9e873..68bbe8696 100644 --- a/.github/workflows/radon.yaml +++ b/.github/workflows/radon.yaml @@ -12,7 +12,7 @@ jobs: pull-requests: read name: "radon" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - uses: davidslusser/actions_python_radon@v1.0.0 with: src: "src" diff --git a/AGENTS.md b/AGENTS.md index 88d6f5680..394b71723 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -191,6 +191,15 @@ uv run make test-e2e # End-to-end tests - **SQLAlchemy**: Database ORM - **Kubernetes**: K8s auth integration +## Pull Request Requirements + +**PR titles MUST start with a JIRA issue key prefix.** CI enforces this via `pr-title-checker` (config: `.github/pr-title-checker-config.json`). + +Allowed prefixes: `LCORE-`, `RSPEED-`, `MGTM-`, `OLS-`, `RHIDP-`, `LEADS-`, `CWFHEALTH-`, `[release/` + +- ✅ `RSPEED-2849: add user_agent to ResponsesEventData` +- ❌ `feat(observability): add user_agent to ResponsesEventData` + ## Development Workflow 1. Use `uv sync --group dev --group llslibdev` for dependencies 2. Always use `uv run` prefix for commands diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eacfc308d..5f352c6e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,7 @@ * [PR description](#pr-description) * [Definition of Done](#definition-of-done) * [A deliverable is to be considered “done” when](#a-deliverable-is-to-be-considered-done-when) +* [Backports](#backports) * [AI assistants](#ai-assistants) * [“Mark” code with substantial AI-generated portions.](#mark-code-with-substantial-ai-generated-portions) * [Copyright and licence notices](#copyright-and-licence-notices) @@ -97,12 +98,72 @@ Happy hacking! ## PR description -* Jira ticket needs to be added into PR title, for example: `LCORE-740: type hints for models unit tests` +* PR titles must start with a JIRA issue key prefix or the target branch of a backport in brackets. CI enforces this via + `pr-title-checker` (config: `.github/pr-title-checker-config.json`). + Allowed prefixes: `LCORE-`, `RSPEED-`, `MGTM-`, `OLS-`, `RHIDP-`, `LEADS-`, + `CWFHEALTH-`, `[release/` + - for example: `LCORE-740: type hints for models unit tests` * Fill-in all relevant information in the PR template - unused parts of PR template (like information about testing etc.) can be deleted * Please note that CodeRabbitAI will create a summary of your pull request + +## Backports + +We use [cherry_picker](https://pypi.org/project/cherry-picker/) to backport +merged pull requests to release branches. See the +[cherry_picker documentation](https://cherry-picker.readthedocs.io/) for full +details. + +### Installation + +```bash +pip install --user cherry_picker +``` + +### Usage + +After a pull request has been merged to `main`, you can backport it to one or +more release branches: + +```bash +# backport to a single release branch +cherry_picker release/0.6 + +# backport to multiple release branches +cherry_picker release/0.5 release/0.6 +``` + +`cherry_picker` will create a new branch, cherry-pick the commit, and open a +pull request against the target release branch. + +If the commit you want to backport is a **merge commit**, append `^-` to the +hash so that `cherry_picker` applies the correct parent diff: + +```bash +cherry_picker ^- release/0.6 +``` + +If there are conflicts, `cherry_picker` will pause and let you resolve them. +After resolving: + +```bash +git add . +cherry_picker --continue +``` + +To abort a backport in progress: + +```bash +cherry_picker --abort +``` + +See the [branching documentation](docs/branching.md) for more details on our +branching strategy and release workflow. + + + ## Definition of Done ### A deliverable is to be considered “done” when @@ -125,7 +186,7 @@ Happy hacking! Nontrivial and substantial AI-generated or AI-assisted content should be “marked” in appropriate cases. In deciding how to approach this, consider adopting one or more of the following recommendations. (This assumes you have -not concluded that a suggestion is a match to some existing third-party code.) +not concluded that a suggestion is a match to some existing third-party code.) In a commit message, or in a pull request/merge request description field, identify the code assistant that you used, perhaps elaborating on how it was @@ -271,14 +332,14 @@ Here is simple example: ```python def function_with_pep484_type_annotations(param1: int, param2: str) -> bool: """Example function with PEP 484 type annotations. - + Args: param1: The first parameter. param2: The second parameter. - + Returns: The return value. True for success, False otherwise. - + Raises: ValueError: If the first parameter does not contain proper model name """ diff --git a/docs/contributing_guide.md b/docs/contributing_guide.md index eacfc308d..5497bd698 100644 --- a/docs/contributing_guide.md +++ b/docs/contributing_guide.md @@ -97,7 +97,11 @@ Happy hacking! ## PR description -* Jira ticket needs to be added into PR title, for example: `LCORE-740: type hints for models unit tests` +* PR titles must start with a JIRA issue key prefix or the target branch of a backport. CI enforces this via + `pr-title-checker` (config: `.github/pr-title-checker-config.json`). + Allowed prefixes: `LCORE-`, `RSPEED-`, `MGTM-`, `OLS-`, `RHIDP-`, `LEADS-`, + `CWFHEALTH-`, `[release/` + - for example: `LCORE-740: type hints for models unit tests` * Fill-in all relevant information in the PR template - unused parts of PR template (like information about testing etc.) can be deleted * Please note that CodeRabbitAI will create a summary of your pull request @@ -125,7 +129,7 @@ Happy hacking! Nontrivial and substantial AI-generated or AI-assisted content should be “marked” in appropriate cases. In deciding how to approach this, consider adopting one or more of the following recommendations. (This assumes you have -not concluded that a suggestion is a match to some existing third-party code.) +not concluded that a suggestion is a match to some existing third-party code.) In a commit message, or in a pull request/merge request description field, identify the code assistant that you used, perhaps elaborating on how it was @@ -271,14 +275,14 @@ Here is simple example: ```python def function_with_pep484_type_annotations(param1: int, param2: str) -> bool: """Example function with PEP 484 type annotations. - + Args: param1: The first parameter. param2: The second parameter. - + Returns: The return value. True for success, False otherwise. - + Raises: ValueError: If the first parameter does not contain proper model name """ diff --git a/docs/demos/lcore/contributing_guidelines.md b/docs/demos/lcore/contributing_guidelines.md index 103c80e78..f9bb68650 100644 --- a/docs/demos/lcore/contributing_guidelines.md +++ b/docs/demos/lcore/contributing_guidelines.md @@ -74,7 +74,10 @@ ptisnovs@redhat.com ## PR description -* Jira ticket needs to be added into PR title +* PR titles must start with a JIRA issue key prefix. CI enforces this via + `pr-title-checker` (config: `.github/pr-title-checker-config.json`). + Allowed prefixes: `LCORE-`, `RSPEED-`, `MGTM-`, `OLS-`, `RHIDP-`, `LEADS-`, + `CWFHEALTH-`, `[release/` - for example: `LCORE-740: type hints for models unit tests` * Fill-in all relevant information in the PR template - unused parts of PR template (like information about testing etc.) can be deleted diff --git a/pyproject.toml b/pyproject.toml index 5f60d4351..da6b1d10f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,8 @@ dependencies = [ # Used by authorization resolvers "jsonpath-ng>=1.6.1", "psycopg2-binary>=2.9.10", - "litellm>=1.75.5.post1", - "urllib3==2.6.3", + "litellm>=1.83.7", + "urllib3>=2.6.3", # Used for agent card configuration "PyYAML>=6.0.0", # Used for rag faiss support diff --git a/scripts/latest-tag.py b/scripts/latest-tag.py new file mode 100755 index 000000000..b4284fb30 --- /dev/null +++ b/scripts/latest-tag.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +"""Determine if the latest tag should be applied.""" + +import os +import subprocess + + +def version_split(value: str) -> tuple[int, ...]: + """Split string into a tuple of ints.""" + try: + return tuple(int(n) for n in value.split(".")) + except ValueError: + return (-1,) + + +def is_prerelease(tag: str) -> bool: + """Determine if a tag is a pre-release version.""" + omit = {"rc", "alpha", "beta", "dev"} + + return any(n in tag for n in omit) + + +def get_latest_stable() -> str | None: + """Return the latest stable tag.""" + stdout = subprocess.check_output(["git", "tag"], text=True) + tags = [tag for tag in stdout.splitlines() if not is_prerelease(tag)] + tags.sort(key=version_split) + + return tags[-1] if tags else None + + +def main() -> None: + """Determine latest tag.""" + if not (current_tag := os.environ.get("GIT_TAG")): + reason = "GIT_TAG environment variable not set, skipping latest tag" + apply_latest = "false" + elif is_prerelease(current_tag): + reason = f"{current_tag} is a pre-release" + apply_latest = "false" + else: + latest_stable = get_latest_stable() + if current_tag == latest_stable: + reason = f"{current_tag} is the latest stable" + apply_latest = "true" + else: + reason = f"{current_tag} is not the latest stable ({latest_stable} is)" + apply_latest = "false" + + print(reason) + + if github_output := os.environ.get("GITHUB_OUTPUT"): + with open(github_output, "a") as f: + f.write(f"apply_latest={apply_latest}\n") + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index e0d1cbcc0..2e88db2cf 100644 --- a/uv.lock +++ b/uv.lock @@ -1627,7 +1627,7 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.0" }, { name = "jsonpath-ng", specifier = ">=1.6.1" }, { name = "kubernetes", specifier = ">=30.1.0" }, - { name = "litellm", specifier = ">=1.75.5.post1" }, + { name = "litellm", specifier = ">=1.83.7" }, { name = "llama-stack", specifier = "==0.4.3" }, { name = "llama-stack-api", specifier = "==0.4.4" }, { name = "llama-stack-client", specifier = "==0.4.3" }, @@ -1640,7 +1640,7 @@ requires-dist = [ { name = "semver", specifier = "<4.0.0" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.42" }, { name = "starlette", specifier = ">=0.47.1" }, - { name = "urllib3", specifier = "==2.6.3" }, + { name = "urllib3", specifier = ">=2.6.3" }, { name = "uvicorn", specifier = ">=0.34.3" }, ] @@ -1707,7 +1707,7 @@ llslibdev = [ [[package]] name = "litellm" -version = "1.82.0" +version = "1.88.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1723,9 +1723,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/00/49bb5c28e0dea0f5086229a2a08d5fdc6c8dc0d8e2acb2a2d1f7dd9f4b70/litellm-1.82.0.tar.gz", hash = "sha256:d388f52447daccbcaafa19a3e68d17b75f1374b5bf2cde680d65e1cd86e50d22", size = 16800355, upload-time = "2026-03-01T02:35:30.363Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ea/f99ececb7f22703fe120f1d8be9ffb749ec9453fbbbbbebc0d6a6b4d7864/litellm-1.88.1.tar.gz", hash = "sha256:89c6b74cc7912d6365793006ff951c0450fe847625008dfe49de8a7dc4529aa5", size = 13885969, upload-time = "2026-06-09T01:06:25.192Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/89/eb28bfcf97d6b045c400e72eb047c381594467048c237dbb6c227764084c/litellm-1.82.0-py3-none-any.whl", hash = "sha256:5496b5d4532cccdc7a095c21cbac4042f7662021c57bc1d17be4e39838929e80", size = 14911978, upload-time = "2026-03-01T02:35:26.844Z" }, + { url = "https://files.pythonhosted.org/packages/42/9a/8f8909201b4bebaf96498c09226f6baa8540086a4c4188ad57d7dfbd97c1/litellm-1.88.1-py3-none-any.whl", hash = "sha256:369b84e57d9426582ddc35e731956ddb6618cda97cc44e4e4d2dfa75982a6e3a", size = 15276206, upload-time = "2026-06-09T01:06:16.72Z" }, ] [[package]] @@ -3828,9 +3828,9 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:4de0ed8cbc457a506dbca40376e206a29efee10756a00f1f3404bf67ad737d04" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:259548471194ab63d7ea273873053a6e3cc23530c1510f01e9d7ad259187bbd0" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e24836d968b54ef4dfb05594001a61958711ac9224026291e4e3f92f83a6fd7f" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:4de0ed8cbc457a506dbca40376e206a29efee10756a00f1f3404bf67ad737d04", upload-time = "2025-10-14T17:26:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:259548471194ab63d7ea273873053a6e3cc23530c1510f01e9d7ad259187bbd0", upload-time = "2025-10-14T17:26:17Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e24836d968b54ef4dfb05594001a61958711ac9224026291e4e3f92f83a6fd7f", upload-time = "2025-10-14T17:26:17Z" }, ] [[package]] @@ -3855,17 +3855,17 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3a651434ae1248b0568c12b5f9e3acc8942eb28378d9d04a79302938b68c6f24" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:28f6eb31b08180a5c5e98d5bc14eef6909c9f5a1dbff9632c3e02a8773449349" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:e438061b87ec7dd6018fca9f975219889aa0a3f6cdc3ea10dd0ae2bc7f1c47ce" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:eb13ff1c34e338d722e76a4fd83b8d282782505bd1b99af4b3c32da66eba6eb4" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:be4438d8dad7f0d5a5e54f0feef8a893446894ec87f102bb1d82dcc4518542e4" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6c9b217584400963d5b4daddb3711ec7a3778eab211e18654fba076cce3b8682" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:728372e3f58c5826445f677746e5311c1935c1a7c59599f73a49ded850e038e8" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:95e56c26f919fbb98f16e7a0b87af494b893f9da9a65a020f17a01c13e520a81" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:6c777160288b08555820781ae0f3a2c67a59bd24b065e88ca1ec20e2f9dc8ac7" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:528fd338311f31c9fb18038cafd00e6eae0bf5ad5577521701acb62510753d18" }, - { url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:d572863990e7d2762b547735ef589f6350d9eb4e441d38753a1c33636698cf4c" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3a651434ae1248b0568c12b5f9e3acc8942eb28378d9d04a79302938b68c6f24", upload-time = "2025-10-14T17:26:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:28f6eb31b08180a5c5e98d5bc14eef6909c9f5a1dbff9632c3e02a8773449349", upload-time = "2025-10-14T17:26:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:e438061b87ec7dd6018fca9f975219889aa0a3f6cdc3ea10dd0ae2bc7f1c47ce", upload-time = "2025-10-14T17:26:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:eb13ff1c34e338d722e76a4fd83b8d282782505bd1b99af4b3c32da66eba6eb4", upload-time = "2025-10-14T17:26:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:be4438d8dad7f0d5a5e54f0feef8a893446894ec87f102bb1d82dcc4518542e4", upload-time = "2025-10-14T17:26:07Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6c9b217584400963d5b4daddb3711ec7a3778eab211e18654fba076cce3b8682", upload-time = "2025-10-14T17:26:07Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:728372e3f58c5826445f677746e5311c1935c1a7c59599f73a49ded850e038e8", upload-time = "2025-10-14T17:26:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:95e56c26f919fbb98f16e7a0b87af494b893f9da9a65a020f17a01c13e520a81", upload-time = "2025-10-14T17:26:08Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:6c777160288b08555820781ae0f3a2c67a59bd24b065e88ca1ec20e2f9dc8ac7", upload-time = "2025-10-14T17:26:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:528fd338311f31c9fb18038cafd00e6eae0bf5ad5577521701acb62510753d18", upload-time = "2025-10-14T17:26:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:d572863990e7d2762b547735ef589f6350d9eb4e441d38753a1c33636698cf4c", upload-time = "2025-10-14T17:26:11Z" }, ] [[package]]