diff --git a/AGENTS.md b/AGENTS.md index 7382123b..b8133f66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,8 @@ Activate the local virtualenv before running any Python/uv commands: `source .ve - Frameworks: `pytest` and `pytest-asyncio`. - Place tests in `tests/`; name files `test_*.py`. +- Write tests in `pytest` style; do not add new `unittest`-based tests or `unittest` + assertions/fixtures. - Run locally with `pytest` before opening a PR (CI runs lint + integration tests). ## Pull Request Guidelines diff --git a/hirundo/_env.py b/hirundo/_env.py index 6ff99f4e..07d613c5 100644 --- a/hirundo/_env.py +++ b/hirundo/_env.py @@ -19,6 +19,19 @@ class EnvLocation(enum.Enum): API_KEY = os.getenv("API_KEY") +def get_env_bool(variable_name: str, default: bool = False) -> bool: + variable_value = os.getenv(variable_name) + if variable_value is None: + return default + + normalized_value = variable_value.strip().lower() + if normalized_value in {"1", "true", "yes", "on"}: + return True + if normalized_value in {"0", "false", "no", "off"}: + return False + return default + + def check_api_key(): if not API_KEY: raise ValueError( diff --git a/hirundo/_llm_sources.py b/hirundo/_llm_sources.py index 03e1a112..d51bde5e 100644 --- a/hirundo/_llm_sources.py +++ b/hirundo/_llm_sources.py @@ -28,6 +28,7 @@ class HuggingFaceTransformersModelOutput(BaseModel): ModelSourceType.HUGGINGFACE_TRANSFORMERS ) model_name: str + token: str | None = None class LocalTransformersModel(BaseModel): diff --git a/hirundo/_model_access.py b/hirundo/_model_access.py new file mode 100644 index 00000000..fb913c0e --- /dev/null +++ b/hirundo/_model_access.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from pathlib import Path + +from huggingface_hub import HfApi +from huggingface_hub.errors import ( + GatedRepoError, + HfHubHTTPError, + RepositoryNotFoundError, +) +from requests import HTTPError + +from hirundo._hirundo_error import HirundoError +from hirundo.logger import get_logger + +logger = get_logger(__name__) + + +def _build_huggingface_access_message( + model_name: str, + model_role: str, + hint: str, + token_provided: bool, +) -> str: + message_prefix = f"The {model_role} model '{model_name}'" + + if hint == "gated": + if token_provided: + return ( + f"{message_prefix} is gated and the provided HuggingFace token does not " + "have access. Please request access or use a different token." + ) + return f"{message_prefix} is gated. Please provide a HuggingFace token with access." + + if hint == "not_found": + if token_provided: + return ( + f"{message_prefix} was not found or is private/gated for the provided " + "token. Please verify the model ID or token access." + ) + return ( + f"{message_prefix} was not found or is private/gated. Please provide a " + "HuggingFace token or verify the model ID." + ) + + if hint == "unauthorized": + if token_provided: + return ( + f"{message_prefix} could not be accessed with the provided HuggingFace " + "token. Please verify token permissions or use a different model." + ) + return ( + f"{message_prefix} could not be accessed without a HuggingFace token. " + "Please provide a token or use a public model." + ) + + return ( + f"{message_prefix} could not be accessed. Please verify the model ID or provide " + "a HuggingFace token with access." + ) + + +def _is_local_model_path(path_or_repo_id: str) -> bool: + potential_path = Path(path_or_repo_id).expanduser() + return potential_path.exists() + + +def _get_huggingface_error_status_code( + exception: HfHubHTTPError | HTTPError, +) -> int | None: + response = exception.response + return response.status_code if response is not None else None + + +def validate_huggingface_model_access( + model_name: str, + token: str | None, + model_role: str, +) -> None: + """Validate that a Hugging Face model can be accessed. + + Args: + model_name: Hugging Face repository ID for the model to validate. + token: Optional Hugging Face access token used for authenticated access. + model_role: Human-readable role for the model in error messages. + """ + huggingface_api = HfApi(token=token) + token_provided = token is not None + + try: + huggingface_api.model_info(repo_id=model_name) + except GatedRepoError as exception: + raise HirundoError( + _build_huggingface_access_message( + model_name=model_name, + model_role=model_role, + hint="gated", + token_provided=token_provided, + ) + ) from exception + except RepositoryNotFoundError as exception: + raise HirundoError( + _build_huggingface_access_message( + model_name=model_name, + model_role=model_role, + hint="not_found", + token_provided=token_provided, + ) + ) from exception + except (HfHubHTTPError, HTTPError) as exception: + status_code = _get_huggingface_error_status_code(exception) + + if status_code in {401, 403}: + hint = "unauthorized" + else: + hint = "generic" + logger.debug( + "HuggingFace access validation failed for %s model '%s' with status %s.", + model_role, + model_name, + status_code, + ) + raise HirundoError( + _build_huggingface_access_message( + model_name=model_name, + model_role=model_role, + hint=hint, + token_provided=token_provided, + ) + ) from exception + + +def validate_judge_model_access(path_or_repo_id: str, token: str | None) -> None: + """Validate that a judge model can be accessed. + + Args: + path_or_repo_id: Local filesystem path or Hugging Face repository ID for the + judge model. + token: Optional Hugging Face access token used when the judge model is hosted + on Hugging Face. + """ + if _is_local_model_path(path_or_repo_id): + return + + validate_huggingface_model_access( + model_name=path_or_repo_id, + token=token, + model_role="judge", + ) diff --git a/hirundo/llm_behavior_eval.py b/hirundo/llm_behavior_eval.py index 561204f2..c020b382 100644 --- a/hirundo/llm_behavior_eval.py +++ b/hirundo/llm_behavior_eval.py @@ -15,6 +15,10 @@ from hirundo._http import raise_for_status_with_reason, requests from hirundo._iter_sse_retrying import aiter_sse_retrying, iter_sse_retrying from hirundo._llm_sources import HuggingFaceTransformersModelOutput, LlmSourcesOutput +from hirundo._model_access import ( + validate_huggingface_model_access, + validate_judge_model_access, +) from hirundo._run_checking import ( DEFAULT_MAX_RETRIES, STATUS_TO_PROGRESS_MAP, @@ -29,6 +33,7 @@ from hirundo.llm_behavior_eval_results import LlmBehaviorEvalResults from hirundo.llm_bias_type import BBQBiasType, UnqoverBiasType from hirundo.logger import get_logger +from hirundo.unlearning_llm import LlmModel from hirundo.unzip import download_and_extract_llm_behavior_eval_zip logger = get_logger(__name__) @@ -64,6 +69,8 @@ class JudgeModel(BaseModel): class EvalRunInfo(BaseModel): + model_config = ConfigDict(protected_namespaces=("model_validate", "model_dump")) + organization_id: int | None = None name: str | None = None model_id: int | None = None @@ -75,7 +82,9 @@ class EvalRunInfo(BaseModel): class OutputLlm(BaseModel): - model_config = {"extra": "allow"} + model_config = ConfigDict( + extra="allow", protected_namespaces=("model_validate", "model_dump") + ) id: int organization_id: int @@ -116,6 +125,8 @@ class LlmEvalMetrics(BaseModel): class EvalRunRecord(BaseModel): + model_config = ConfigDict(protected_namespaces=("model_validate", "model_dump")) + id: int name: str model_id: int | None @@ -143,6 +154,27 @@ class LlmBehaviorEval: def __init__(self, run_id: str | None = None): self.run_id = run_id + @staticmethod + def _validate_model_access(model_or_run: ModelOrRun, run_info: EvalRunInfo) -> None: + if run_info.judge_model is not None: + validate_judge_model_access( + path_or_repo_id=run_info.judge_model.path_or_repo_id, + token=run_info.judge_model.token, + ) + if model_or_run == ModelOrRun.MODEL and run_info.model_id is None: + raise HirundoLlmBehaviorEvalError( + "model_id is required when model_or_run is 'model'" + ) + + if model_or_run == ModelOrRun.MODEL and run_info.model_id is not None: + llm_model = LlmModel.get_by_id(run_info.model_id) + if isinstance(llm_model.model_source, HuggingFaceTransformersModelOutput): + validate_huggingface_model_access( + model_name=llm_model.model_source.model_name, + token=llm_model.model_source.token, + model_role="LLM", + ) + @staticmethod def _parse_eval_run_record(response_payload: dict) -> EvalRunRecord: model_payload = response_payload.get("model") @@ -219,6 +251,8 @@ def launch_eval_run( else: model_or_run_value = model_or_run + LlmBehaviorEval._validate_model_access(model_or_run_value, run_info) + response = requests.post( f"{API_HOST}/llm-behavior-eval/run/{model_or_run_value.value}", json=run_info.model_dump(mode="json"), diff --git a/hirundo/llm_behavior_eval_results.py b/hirundo/llm_behavior_eval_results.py index f13e9e0b..65f2e0f6 100644 --- a/hirundo/llm_behavior_eval_results.py +++ b/hirundo/llm_behavior_eval_results.py @@ -1,13 +1,16 @@ import typing from pathlib import Path -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict T = typing.TypeVar("T") class LlmBehaviorEvalResults(BaseModel, typing.Generic[T]): - model_config = {"arbitrary_types_allowed": True} + model_config = ConfigDict( + arbitrary_types_allowed=True, + protected_namespaces=("model_validate", "model_dump"), + ) cached_zip_path: Path """ diff --git a/hirundo/unlearning_llm.py b/hirundo/unlearning_llm.py index 00375dc1..060e4ecf 100644 --- a/hirundo/unlearning_llm.py +++ b/hirundo/unlearning_llm.py @@ -8,11 +8,16 @@ from tqdm import tqdm from tqdm.contrib.logging import logging_redirect_tqdm -from hirundo._env import API_HOST +from hirundo._env import API_HOST, get_env_bool from hirundo._headers import get_headers from hirundo._http import raise_for_status_with_reason, requests from hirundo._llm_pipeline import get_hf_pipeline_for_run_given_model -from hirundo._llm_sources import LlmSources, LlmSourcesOutput +from hirundo._llm_sources import ( + HuggingFaceTransformersModel, + LlmSources, + LlmSourcesOutput, +) +from hirundo._model_access import validate_huggingface_model_access from hirundo._run_checking import ( STATUS_TO_PROGRESS_MAP, aiter_run_events, @@ -48,7 +53,21 @@ class LlmModel(BaseModel): def create( self, replace_if_exists: bool = False, + validate_hf_access: bool = True, ) -> int: + should_validate_hf_access = validate_hf_access or get_env_bool( + "HIRUNDO_VALIDATE_HF_ACCESS", + default=True, + ) + + if should_validate_hf_access and isinstance( + self.model_source, HuggingFaceTransformersModel + ): + validate_huggingface_model_access( + model_name=self.model_source.model_name, + token=self.model_source.token, + model_role="LLM", + ) llm_model_response = requests.post( f"{API_HOST}/unlearning-llm/llm/", json={ diff --git a/pyproject.toml b/pyproject.toml index c246471e..ff0cec33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "stamina>=24.2.0", "httpx-sse>=0.4.0", "tqdm>=4.66.5", + "huggingface-hub>=1.0.0", "h11>=0.16.0", # ⬆️ Required to fix vulnerability GHSA-vqfr-h8mv-ghfj "requests>=2.32.4", @@ -66,8 +67,8 @@ dev = [ "basedpyright==1.37.1", "virtualenv>=20.36.1", # ⬆️ Needed for `pre-commit` version fix for vulnerability GHSA-rqc4-2hc7-8c8v - "authlib>=1.6.6", - # ⬆️ Required to fix vulnerability CVE-2025-68158 + "authlib>=1.6.7", + # ⬆️ Required to fix vulnerability CVE-2026-28802 "ruff>=0.12.0", "bumpver>=2025.1131", "platformdirs>=4.3.6", diff --git a/tests/dataset_qa_shared.py b/tests/dataset_qa_shared.py index 014b3957..6c9c0ba5 100644 --- a/tests/dataset_qa_shared.py +++ b/tests/dataset_qa_shared.py @@ -1,4 +1,3 @@ -import os from collections import defaultdict from contextlib import contextmanager @@ -9,6 +8,7 @@ RunArgs, StorageConfig, ) +from hirundo._env import get_env_bool from hirundo._run_status import RunStatus from hirundo.logger import get_logger @@ -168,8 +168,8 @@ def dataset_qa_sync_test( run_args: RunArgs | None = None, ): logger.info("Sync: Finished cleanup") - if (os.getenv("FULL_TEST", "false") == "true" and sanity) or ( - alternative_env and os.getenv(alternative_env, "false") == "true" + if (get_env_bool("FULL_TEST") and sanity) or ( + alternative_env and get_env_bool(alternative_env) ): run_id = test_dataset.run_qa(replace_dataset_if_exists=True, run_args=run_args) logger.info("Sync: Started dataset QA run with run ID %s", run_id) @@ -189,7 +189,7 @@ async def dataset_qa_async_test( run_args: RunArgs | None = None, ): logger.info("Async: Finished cleanup") - if os.getenv(env, "false") == "true": + if get_env_bool(env): run_id = test_dataset.run_qa(replace_dataset_if_exists=True, run_args=run_args) logger.info("Async: Started dataset QA run with run ID %s", run_id) events_generator = test_dataset.acheck_run() diff --git a/tests/llm-behavior-eval/llm_behavior_eval_test.py b/tests/llm-behavior-eval/llm_behavior_eval_test.py index 090d1a82..c0668afd 100644 --- a/tests/llm-behavior-eval/llm_behavior_eval_test.py +++ b/tests/llm-behavior-eval/llm_behavior_eval_test.py @@ -1,5 +1,4 @@ import logging -import os from hirundo import ( BBQBiasType, @@ -10,6 +9,7 @@ ModelOrRun, PresetType, ) +from hirundo._env import get_env_bool from tests.testing_utils import get_unique_id logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ def test_llm_behavior_eval(): bias_type=BBQBiasType.ALL, ) assert llm_id is not None - if os.getenv("FULL_TEST", "false") == "true": + if get_env_bool("FULL_TEST"): run_id = LlmBehaviorEval.launch_eval_run(ModelOrRun.MODEL, run_info) assert run_id is not None results = LlmBehaviorEval.check_run_by_id(run_id) diff --git a/tests/test_llm_behavior_eval_model_access.py b/tests/test_llm_behavior_eval_model_access.py new file mode 100644 index 00000000..f5218e6d --- /dev/null +++ b/tests/test_llm_behavior_eval_model_access.py @@ -0,0 +1,55 @@ +import secrets + +import hirundo.llm_behavior_eval as llm_behavior_eval_module +import pytest +from hirundo._llm_sources import HuggingFaceTransformersModelOutput +from hirundo.llm_behavior_eval import EvalRunInfo, LlmBehaviorEval, ModelOrRun + + +def test_validate_model_access_forwards_huggingface_token( + monkeypatch: pytest.MonkeyPatch, +) -> None: + run_info = EvalRunInfo(model_id=123) + user_access_secret = secrets.token_hex(8) + llm_model_output = type( + "LlmModelStub", + (), + { + "model_source": HuggingFaceTransformersModelOutput( + model_name="org/private-model", + token=user_access_secret, + ) + }, + )() + captured_call: dict[str, str | None] = {} + + monkeypatch.setattr( + llm_behavior_eval_module.LlmModel, + "get_by_id", + lambda model_id: llm_model_output, + ) + + def fake_validate_huggingface_model_access( + model_name: str, token: str | None, model_role: str + ) -> None: + captured_call.update( + { + "model_name": model_name, + "token": token, + "model_role": model_role, + } + ) + + monkeypatch.setattr( + llm_behavior_eval_module, + "validate_huggingface_model_access", + fake_validate_huggingface_model_access, + ) + + LlmBehaviorEval._validate_model_access(ModelOrRun.MODEL, run_info) + + assert captured_call == { + "model_name": "org/private-model", + "token": user_access_secret, + "model_role": "LLM", + } diff --git a/tests/test_model_access.py b/tests/test_model_access.py new file mode 100644 index 00000000..b8cf1d84 --- /dev/null +++ b/tests/test_model_access.py @@ -0,0 +1,161 @@ +import secrets +from pathlib import Path +from typing import Any, TypeVar, cast + +import hirundo._model_access as model_access_module +import httpx +import pytest +from hirundo._hirundo_error import HirundoError +from hirundo._model_access import ( + validate_huggingface_model_access, + validate_judge_model_access, +) +from huggingface_hub.errors import ( + GatedRepoError, + HfHubHTTPError, + RepositoryNotFoundError, +) +from requests import HTTPError, Response + + +def _build_http_error(status_code: int) -> HTTPError: + response_object = Response() + response_object.status_code = status_code + return HTTPError(response=response_object) + + +def _build_huggingface_error_response(status_code: int) -> httpx.Response: + return httpx.Response( + status_code=status_code, + request=httpx.Request("GET", "https://huggingface.co/api/models/test"), + ) + + +ExceptionType = TypeVar("ExceptionType", bound=HfHubHTTPError) + + +def _build_huggingface_hub_error( + exception_type: type[ExceptionType], + message: str, + status_code: int, +) -> ExceptionType: + return cast("Any", exception_type)( + message, + response=_build_huggingface_error_response(status_code=status_code), + ) + + +def test_validate_huggingface_model_access_allows_public_model( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured_repo_ids: list[str] = [] + + def fake_model_info(_self: object, *, repo_id: str) -> None: + captured_repo_ids.append(repo_id) + + monkeypatch.setattr(model_access_module.HfApi, "model_info", fake_model_info) + + validate_huggingface_model_access( + model_name="some/model", + token=None, + model_role="LLM", + ) + + assert captured_repo_ids == ["some/model"] + + +def test_validate_huggingface_model_access_raises_gated_message_without_token( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def fake_model_info(_self: object, *, repo_id: str) -> None: + raise _build_huggingface_hub_error(GatedRepoError, "gated", status_code=403) + + monkeypatch.setattr(model_access_module.HfApi, "model_info", fake_model_info) + + with pytest.raises(HirundoError, match="is gated"): + validate_huggingface_model_access( + model_name="some/model", + token=None, + model_role="judge", + ) + + +def test_validate_huggingface_model_access_raises_not_found_message_with_token( + monkeypatch: pytest.MonkeyPatch, +) -> None: + user_access_secret = secrets.token_hex(8) + + def fake_model_info(_self: object, *, repo_id: str) -> None: + raise _build_huggingface_hub_error( + RepositoryNotFoundError, "missing", status_code=404 + ) + + monkeypatch.setattr(model_access_module.HfApi, "model_info", fake_model_info) + + with pytest.raises(HirundoError, match="provided token"): + validate_huggingface_model_access( + model_name="missing/model", + token=user_access_secret, + model_role="LLM", + ) + + +def test_validate_huggingface_model_access_raises_unauthorized_message( + monkeypatch: pytest.MonkeyPatch, +) -> None: + user_access_secret = secrets.token_hex(8) + + def fake_model_info(_self: object, *, repo_id: str) -> None: + raise _build_huggingface_hub_error( + HfHubHTTPError, "unauthorized", status_code=401 + ) + + monkeypatch.setattr(model_access_module.HfApi, "model_info", fake_model_info) + + with pytest.raises(HirundoError, match="provided HuggingFace token"): + validate_huggingface_model_access( + model_name="private/model", + token=user_access_secret, + model_role="judge", + ) + + +def test_validate_huggingface_model_access_keeps_legacy_http_error_support( + monkeypatch: pytest.MonkeyPatch, +) -> None: + user_access_secret = secrets.token_hex(8) + + def fake_model_info(_self: object, *, repo_id: str) -> None: + raise _build_http_error(status_code=401) + + monkeypatch.setattr(model_access_module.HfApi, "model_info", fake_model_info) + + with pytest.raises(HirundoError, match="provided HuggingFace token"): + validate_huggingface_model_access( + model_name="private/model", + token=user_access_secret, + model_role="judge", + ) + + +def test_validate_judge_model_access_skips_local_path( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + local_model_path = tmp_path / "local_model" + local_model_path.mkdir() + validate_calls: list[tuple[str, str | None, str]] = [] + + def fake_validate_huggingface_model_access( + model_name: str, token: str | None, model_role: str + ) -> None: + validate_calls.append((model_name, token, model_role)) + + monkeypatch.setattr( + model_access_module, + "validate_huggingface_model_access", + fake_validate_huggingface_model_access, + ) + + validate_judge_model_access(str(local_model_path), token=None) + + assert validate_calls == [] diff --git a/tests/test_unlearning_llm_model_create.py b/tests/test_unlearning_llm_model_create.py new file mode 100644 index 00000000..508e2b42 --- /dev/null +++ b/tests/test_unlearning_llm_model_create.py @@ -0,0 +1,106 @@ +import secrets + +import hirundo.unlearning_llm as unlearning_llm_module +import pytest +from hirundo._llm_sources import HuggingFaceTransformersModel +from hirundo.unlearning_llm import LlmModel + + +class _FakeResponse: + def __init__(self, model_id: int) -> None: + self._model_id = model_id + + def json(self) -> dict[str, int]: + return {"id": self._model_id} + + +def _build_huggingface_llm_model() -> LlmModel: + user_access_secret = secrets.token_hex(8) + return LlmModel( + model_name="test-llm", + model_source=HuggingFaceTransformersModel( + model_name="org/private-model", + token=user_access_secret, + ), + ) + + +def _stub_create_dependencies(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + unlearning_llm_module.requests, + "post", + lambda *args, **kwargs: _FakeResponse(model_id=123), + ) + monkeypatch.setattr( + unlearning_llm_module, + "raise_for_status_with_reason", + lambda _response: None, + ) + + +def test_llm_model_create_skips_hf_access_validation_by_default( + monkeypatch: pytest.MonkeyPatch, +) -> None: + validation_calls: list[tuple[str, str | None, str]] = [] + + monkeypatch.setenv("HIRUNDO_VALIDATE_HF_ACCESS", "false") + _stub_create_dependencies(monkeypatch) + monkeypatch.setattr( + unlearning_llm_module, + "validate_huggingface_model_access", + lambda model_name, token, model_role: validation_calls.append( + (model_name, token, model_role) + ), + ) + + _build_huggingface_llm_model().create() + + assert validation_calls == [] + + +def test_llm_model_create_validates_hf_access_when_parameter_enabled( + monkeypatch: pytest.MonkeyPatch, +) -> None: + validation_calls: list[tuple[str, str | None, str]] = [] + + monkeypatch.setenv("HIRUNDO_VALIDATE_HF_ACCESS", "false") + _stub_create_dependencies(monkeypatch) + monkeypatch.setattr( + unlearning_llm_module, + "validate_huggingface_model_access", + lambda model_name, token, model_role: validation_calls.append( + (model_name, token, model_role) + ), + ) + + llm_model = _build_huggingface_llm_model() + llm_model.create(validate_hf_access=True) + + assert isinstance(llm_model.model_source, HuggingFaceTransformersModel) + assert validation_calls == [ + ("org/private-model", llm_model.model_source.token, "LLM") + ] + + +def test_llm_model_create_validates_hf_access_when_env_enabled( + monkeypatch: pytest.MonkeyPatch, +) -> None: + validation_calls: list[tuple[str, str | None, str]] = [] + + monkeypatch.setenv("HIRUNDO_VALIDATE_HF_ACCESS", "true") + _stub_create_dependencies(monkeypatch) + monkeypatch.setattr( + unlearning_llm_module, + "validate_huggingface_model_access", + lambda model_name, token, model_role: validation_calls.append( + (model_name, token, model_role) + ), + ) + + llm_model = _build_huggingface_llm_model() + llm_model.create() + + assert isinstance(llm_model.model_source, HuggingFaceTransformersModel) + assert validation_calls == [ + ("org/private-model", llm_model.model_source.token, "LLM") + ] diff --git a/tests/unlearning-llm/unlearn_llm_behavior_test.py b/tests/unlearning-llm/unlearn_llm_behavior_test.py index 3506c982..a405f228 100644 --- a/tests/unlearning-llm/unlearn_llm_behavior_test.py +++ b/tests/unlearning-llm/unlearn_llm_behavior_test.py @@ -1,5 +1,4 @@ import logging -import os from hirundo import ( BBQBiasType, @@ -8,6 +7,7 @@ LlmModel, LlmUnlearningRun, ) +from hirundo._env import get_env_bool from tests.testing_utils import get_unique_id from transformers.pipelines.base import Pipeline @@ -28,7 +28,7 @@ def test_unlearn_llm_behavior(): bias_type=BBQBiasType.ALL, ) assert llm_id is not None - if os.getenv("FULL_TEST", "false") == "true": + if get_env_bool("FULL_TEST"): run_id = LlmUnlearningRun.launch( llm_id, run_info, diff --git a/uv.lock b/uv.lock index 4937fdfd..5dfab3a6 100644 --- a/uv.lock +++ b/uv.lock @@ -83,14 +83,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.6" +version = "1.6.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/9b/b1661026ff24bc641b76b78c5222d614776b0c085bcfdac9bd15a1cb4b35/authlib-1.6.6.tar.gz", hash = "sha256:45770e8e056d0f283451d9996fbb59b70d45722b45d854d58f32878d0a40c38e", size = 164894, upload-time = "2025-12-12T08:01:41.464Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134, upload-time = "2026-03-02T07:44:01.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/51/321e821856452f7386c4e9df866f196720b1ad0c5ea1623ea7399969ae3b/authlib-1.6.6-py2.py3-none-any.whl", hash = "sha256:7d9e9bc535c13974313a87f53e8430eb6ea3d1cf6ae4f6efcd793f2e949143fd", size = 244005, upload-time = "2025-12-12T08:01:40.209Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197, upload-time = "2026-03-02T07:44:00.307Z" }, ] [[package]] @@ -624,31 +624,34 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.2.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, - { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, - { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" }, - { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" }, - { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" }, - { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" }, - { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" }, - { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, - { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, - { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, - { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/68/01/928fd82663fb0ab455551a178303a2960e65029da66b21974594f3a20a94/hf_xet-1.4.0.tar.gz", hash = "sha256:48e6ba7422b0885c9bbd8ac8fdf5c4e1306c3499b82d489944609cc4eae8ecbd", size = 660350, upload-time = "2026-03-11T18:50:03.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4b/2351e30dddc6f3b47b3da0a0693ec1e82f8303b1a712faa299cf3552002b/hf_xet-1.4.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:76725fcbc5f59b23ac778f097d3029d6623e3cf6f4057d99d1fce1a7e3cff8fc", size = 3796397, upload-time = "2026-03-11T18:49:47.382Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/3db90ec0afb4e26e3330b1346b89fe0e9a3b7bfc2d6a2b2262787790d25f/hf_xet-1.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:76f1f73bee81a6e6f608b583908aa24c50004965358ac92c1dc01080a21bcd09", size = 3556235, upload-time = "2026-03-11T18:49:45.785Z" }, + { url = "https://files.pythonhosted.org/packages/57/6e/2a662af2cbc6c0a64ebe9fcdb8faf05b5205753d45a75a3011bb2209d0b4/hf_xet-1.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1818c2e5d6f15354c595d5111c6eb0e5a30a6c5c1a43eeaec20f19607cff0b34", size = 4213145, upload-time = "2026-03-11T18:49:38.009Z" }, + { url = "https://files.pythonhosted.org/packages/b9/4a/47c129affb540767e0e3e101039a95f4a73a292ec689c26e8f0c5b633f9d/hf_xet-1.4.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:70764d295f485db9cc9a6af76634ea00ec4f96311be7485f8f2b6144739b4ccf", size = 3991951, upload-time = "2026-03-11T18:49:36.396Z" }, + { url = "https://files.pythonhosted.org/packages/76/81/ec516cfc6281cfeef027b0919166b2fe11ab61fbe6131a2c43fafbed8b68/hf_xet-1.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d3bd2a1e289f772c715ca88cdca8ceb3d8b5c9186534d5925410e531d849a3e", size = 4193205, upload-time = "2026-03-11T18:49:54.415Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/0945b5e542ed6c6ce758b589b27895a449deab630dfcdee5a6ee0f699d21/hf_xet-1.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:06da3797f1fdd9a8f8dbc8c1bddfa0b914789b14580c375d29c32ee35c2c66ca", size = 4431022, upload-time = "2026-03-11T18:49:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ad/a4859c55ab4b67a4fde2849be8bde81917f54062050419b821071f199a9c/hf_xet-1.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:30b9d8f384ccec848124d51d883e91f3c88d430589e02a7b6d867730ab8d53ac", size = 3674977, upload-time = "2026-03-11T18:50:06.369Z" }, + { url = "https://files.pythonhosted.org/packages/4b/17/5bf3791e3a53e597913c2a775a48a98aaded9c2ddb5d1afaedabb55e2ed8/hf_xet-1.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:07ffdbf7568fa3245b24d949f0f3790b5276fb7293a5554ac4ec02e5f7e2b38d", size = 3536778, upload-time = "2026-03-11T18:50:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a1/05a7f9d6069bf78405d3fc2464b6c76b167128501e13b4f1d6266e1d1f54/hf_xet-1.4.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e2731044f3a18442f9f7a3dcf03b96af13dee311f03846a1df1f0553a3ea0fc6", size = 3796727, upload-time = "2026-03-11T18:49:52.889Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8a/67abc642c2b32efcb7a257cdad8555c2904e23f18a1b4fec3aef1ebfe0fc/hf_xet-1.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b6f3729335fbc4baef60fe14fe32ef13ac9d377bdc898148c541e20c6056b504", size = 3555869, upload-time = "2026-03-11T18:49:51.313Z" }, + { url = "https://files.pythonhosted.org/packages/19/3d/4765367c64ee70db15fa771d5b94bf12540b85076a1d3210ebbfec42d477/hf_xet-1.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c0c9f052738a024073d332c573275c8e33697a3ef3f5dd2fb4ef98216e1e74a", size = 4212980, upload-time = "2026-03-11T18:49:44.21Z" }, + { url = "https://files.pythonhosted.org/packages/0e/bf/6ad99ee0e7ca2318f912a87318e493d82d8f9aace6be81f774bd14b996df/hf_xet-1.4.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f44b2324be75bfa399735996ac299fd478684c48ce47d12a42b5f24b1a99ccb8", size = 3991136, upload-time = "2026-03-11T18:49:42.512Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/932e25c69699076088f57e3c14f83ccae87bac25e755994f3362acc908d5/hf_xet-1.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:01de78b1ceddf8b38da001f7cc728b3bc3eb956948b18e8a1997ad6fc80fbe9d", size = 4192676, upload-time = "2026-03-11T18:50:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0a/5e41339a294fd3450948989a47ecba9824d5bc1950cf767f928ecaf53a55/hf_xet-1.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cac8616e7a974105c3494735313f5ab0fb79b5accadec1a7a992859a15536a9", size = 4430729, upload-time = "2026-03-11T18:50:01.923Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c1/c3d8ed9b7118e9166b0cf71dfd501da82f1abe306387e34e0f3ee59553ec/hf_xet-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3a5d9cb25095ceb3beab4843ae2d1b3e5746371ddbf2e5849f7be6a7d6f44df4", size = 3674989, upload-time = "2026-03-11T18:50:12.633Z" }, + { url = "https://files.pythonhosted.org/packages/65/bc/ea26cf774063cb09d7aaaa6cba9d341fb72b42ea99b8a94ca254dbafbbb0/hf_xet-1.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9b777674499dc037317db372c90a2dd91329b5f1ee93c645bb89155bb974f5bf", size = 3536805, upload-time = "2026-03-11T18:50:11.082Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f9/a0b01945726aea81d2f213457cd5f5102a51e6fd1ca9f9769f561fb57501/hf_xet-1.4.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:981d2b5222c3baadf9567c135cf1d1073786f546b7745686978d46b5df179e16", size = 3799223, upload-time = "2026-03-11T18:49:49.884Z" }, + { url = "https://files.pythonhosted.org/packages/5d/30/ee62b0c00412f49a7e6f509f0104ee8808692278d247234336df48029349/hf_xet-1.4.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:cc8bd050349d0d7995ce7b3a3a18732a2a8062ce118a82431602088abb373428", size = 3560682, upload-time = "2026-03-11T18:49:48.633Z" }, + { url = "https://files.pythonhosted.org/packages/93/d0/0fe5c44dbced465a651a03212e1135d0d7f95d19faada692920cb56f8e38/hf_xet-1.4.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5d0c38d2a280d814280b8c15eead4a43c9781e7bf6fc37843cffab06dcdc76b9", size = 4218323, upload-time = "2026-03-11T18:49:40.921Z" }, + { url = "https://files.pythonhosted.org/packages/73/df/7b3c99a4e50442039eae498e5c23db634538eb3e02214109880cf1165d4c/hf_xet-1.4.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6a883f0250682ea888a1bd0af0631feda377e59ad7aae6fb75860ecee7ae0f93", size = 3997156, upload-time = "2026-03-11T18:49:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/47dfedf271c21d95346660ae1698e7ece5ab10791fa6c4f20c59f3713083/hf_xet-1.4.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:99e1d9255fe8ecdf57149bb0543d49e7b7bd8d491ddf431eb57e114253274df5", size = 4199052, upload-time = "2026-03-11T18:49:57.097Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c0/346b9aad1474e881e65f998d5c1981695f0af045bc7a99204d9d86759a89/hf_xet-1.4.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b25f06ce42bd2d5f2e79d4a2d72f783d3ac91827c80d34a38cf8e5290dd717b0", size = 4434346, upload-time = "2026-03-11T18:49:58.67Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d6/88ce9d6caa397c3b935263d5bcbe3ebf6c443f7c76098b8c523d206116b9/hf_xet-1.4.0-cp37-abi3-win_amd64.whl", hash = "sha256:8d6d7816d01e0fa33f315c8ca21b05eca0ce4cdc314f13b81d953e46cc6db11d", size = 3678921, upload-time = "2026-03-11T18:50:09.496Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/17d99ed253b28a9550ca479867c66a8af4c9bcd8cdc9a26b0c8007c2000a/hf_xet-1.4.0-cp37-abi3-win_arm64.whl", hash = "sha256:cb8d9549122b5b42f34b23b14c6b662a88a586a919d418c774d8dbbc4b3ce2aa", size = 3541054, upload-time = "2026-03-11T18:50:07.963Z" }, ] [[package]] @@ -660,6 +663,7 @@ dependencies = [ { name = "h11" }, { name = "httpx" }, { name = "httpx-sse" }, + { name = "huggingface-hub" }, { name = "pydantic" }, { name = "python-dotenv" }, { name = "pyyaml" }, @@ -734,6 +738,7 @@ requires-dist = [ { name = "h11", specifier = ">=0.16.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "httpx-sse", specifier = ">=0.4.0" }, + { name = "huggingface-hub", specifier = ">=1.0.0" }, { name = "pandas", marker = "extra == 'pandas'", specifier = ">=2.2.3" }, { name = "peft", marker = "extra == 'transformers'", specifier = ">=0.18.1" }, { name = "polars", marker = "extra == 'polars'", specifier = ">=1.0.0" }, @@ -758,7 +763,7 @@ deploy = [ { name = "twine", specifier = ">=5.0.0" }, ] dev = [ - { name = "authlib", specifier = ">=1.6.6" }, + { name = "authlib", specifier = ">=1.6.7" }, { name = "basedpyright", specifier = "==1.37.1" }, { name = "bumpver", specifier = ">=2025.1131" }, { name = "cryptography", specifier = ">=46.0.5" }, @@ -831,21 +836,22 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.36.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { 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 = "requests" }, { name = "tqdm" }, + { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/63/4910c5fa9128fdadf6a9c5ac138e8b1b6cee4ca44bf7915bbfbce4e355ee/huggingface_hub-0.36.0.tar.gz", hash = "sha256:47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25", size = 463358, upload-time = "2025-10-23T12:12:01.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/7a/304cec37112382c4fe29a43bcb0d5891f922785d18745883d2aa4eb74e4b/huggingface_hub-1.6.0.tar.gz", hash = "sha256:d931ddad8ba8dfc1e816bf254810eb6f38e5c32f60d4184b5885662a3b167325", size = 717071, upload-time = "2026-03-06T14:19:18.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/92/e3/e3a44f54c8e2f28983fcf07f13d4260b37bd6a0d3a081041bc60b91d230e/huggingface_hub-1.6.0-py3-none-any.whl", hash = "sha256:ef40e2d5cb85e48b2c067020fa5142168342d5108a1b267478ed384ecbf18961", size = 612874, upload-time = "2026-03-06T14:19:16.844Z" }, ] [[package]] @@ -2711,24 +2717,23 @@ wheels = [ [[package]] name = "transformers" -version = "4.57.6" +version = "5.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, { 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.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, - { name = "requests" }, { name = "safetensors" }, { name = "tokenizers" }, { name = "tqdm" }, + { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/1a/70e830d53ecc96ce69cfa8de38f163712d2b43ac52fbd743f39f56025c31/transformers-5.3.0.tar.gz", hash = "sha256:009555b364029da9e2946d41f1c5de9f15e6b1df46b189b7293f33a161b9c557", size = 8830831, upload-time = "2026-03-04T17:41:46.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" }, + { url = "https://files.pythonhosted.org/packages/b8/88/ae8320064e32679a5429a2c9ebbc05c2bf32cefb6e076f9b07f6d685a9b4/transformers-5.3.0-py3-none-any.whl", hash = "sha256:50ac8c89c3c7033444fb3f9f53138096b997ebb70d4b5e50a2e810bf12d3d29a", size = 10661827, upload-time = "2026-03-04T17:41:42.722Z" }, ] [[package]]