diff --git a/benchmarks/runner.py b/benchmarks/runner.py index 5b837abb..97769cd4 100644 --- a/benchmarks/runner.py +++ b/benchmarks/runner.py @@ -263,9 +263,7 @@ def _update_progress( # Call custom callback if provided if self.config.progress_callback: - self.config.progress_callback( - stats.completed, stats.total, sample_result, stats - ) + self.config.progress_callback(stats.completed, stats.total, sample_result, stats) # Update display based on mode if progress_mode == "tqdm" and pbar is not None: @@ -282,7 +280,7 @@ def _update_progress( if stats.completed % interval == 0 or stats.completed == stats.total: print( f" Progress: {stats.completed}/{stats.total} " - f"({stats.completed/stats.total:.0%}) | " + f"({stats.completed / stats.total:.0%}) | " f"Acc: {stats.accuracy:.1%} | " f"Errors: {stats.errors} | " f"ETA: {self._format_eta(stats.eta_seconds)}" diff --git a/examples/oolong_example.py b/examples/oolong_example.py index 69f3043b..b5ca0634 100644 --- a/examples/oolong_example.py +++ b/examples/oolong_example.py @@ -32,9 +32,7 @@ def load_random_oolong_row() -> dict: def main(): # Parse command-line arguments - parser = argparse.ArgumentParser( - description="Run Oolong benchmark example with RLM" - ) + parser = argparse.ArgumentParser(description="Run Oolong benchmark example with RLM") parser.add_argument( "--backend", type=str, diff --git a/examples/subprocess_repl_demo.py b/examples/subprocess_repl_demo.py index 17031c65..2642edfd 100644 --- a/examples/subprocess_repl_demo.py +++ b/examples/subprocess_repl_demo.py @@ -47,6 +47,7 @@ def demonstrate_isolation(): print(f"Platform: {platform.system()}") if platform.system() == "Linux": import shutil + if not shutil.which("bwrap"): print("Note: bubblewrap not installed - filesystem sandbox disabled") print() diff --git a/rlm/environments/subprocess_repl.py b/rlm/environments/subprocess_repl.py index 0ceef81f..bb6c3106 100644 --- a/rlm/environments/subprocess_repl.py +++ b/rlm/environments/subprocess_repl.py @@ -298,9 +298,22 @@ def __init__( # Pre-approved packages (stdlib + user-specified) self.allowed_packages: set[str] = { - "json", "re", "math", "collections", "itertools", - "functools", "datetime", "random", "string", "typing", - "os", "sys", "io", "time", "pathlib", "copy", + "json", + "re", + "math", + "collections", + "itertools", + "functools", + "datetime", + "random", + "string", + "typing", + "os", + "sys", + "io", + "time", + "pathlib", + "copy", } if allowed_packages: self.allowed_packages.update(allowed_packages) @@ -472,10 +485,12 @@ def _install_package(self, package: str): capture_output=True, ) self._installed_packages.add(package) - self._overhead_stats["package_installs"].append({ - "package": package, - "time_ms": (time.perf_counter() - start) * 1000, - }) + self._overhead_stats["package_installs"].append( + { + "package": package, + "time_ms": (time.perf_counter() - start) * 1000, + } + ) def _extract_missing_module(self, stderr: str) -> str | None: """Extract module name from ImportError/ModuleNotFoundError.""" @@ -580,24 +595,38 @@ def _linux_sandbox_wrap(self, cmd: list[str]) -> list[str]: bwrap_cmd = [ "bwrap", - "--ro-bind", "/usr", "/usr", - "--ro-bind", "/lib", "/lib", - "--ro-bind", "/bin", "/bin", - "--ro-bind", "/sbin", "/sbin", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/bin", + "/bin", + "--ro-bind", + "/sbin", + "/sbin", ] # Add /lib64 if it exists if os.path.exists("/lib64"): bwrap_cmd.extend(["--ro-bind", "/lib64", "/lib64"]) - bwrap_cmd.extend([ - "--ro-bind", self.venv_path, self.venv_path, - "--bind", self.temp_dir, self.temp_dir, - "--unshare-net", - "--unshare-pid", - "--die-with-parent", - "--", - ]) + bwrap_cmd.extend( + [ + "--ro-bind", + self.venv_path, + self.venv_path, + "--bind", + self.temp_dir, + self.temp_dir, + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--", + ] + ) return bwrap_cmd + cmd @@ -621,11 +650,13 @@ def execute_code(self, code: str) -> REPLResult: total_time = time.perf_counter() - total_start # Track overhead - self._overhead_stats["executions"].append({ - "total_ms": total_time * 1000, - "code_ms": (result.execution_time or 0) * 1000, - "overhead_ms": (total_time - (result.execution_time or 0)) * 1000, - }) + self._overhead_stats["executions"].append( + { + "total_ms": total_time * 1000, + "code_ms": (result.execution_time or 0) * 1000, + "overhead_ms": (total_time - (result.execution_time or 0)) * 1000, + } + ) return result @@ -638,13 +669,15 @@ def _try_execute(self, code: str) -> REPLResult: # Build environment - inherit essential vars for macOS compatibility env = os.environ.copy() - env.update({ - "PATH": os.path.join(self.venv_path, "bin") + ":/usr/bin:/bin", - "HOME": self.temp_dir, - "TMPDIR": self.temp_dir, - "PYTHONDONTWRITEBYTECODE": "1", - "PYTHONNOUSERSITE": "1", - }) + env.update( + { + "PATH": os.path.join(self.venv_path, "bin") + ":/usr/bin:/bin", + "HOME": self.temp_dir, + "TMPDIR": self.temp_dir, + "PYTHONDONTWRITEBYTECODE": "1", + "PYTHONNOUSERSITE": "1", + } + ) try: result = subprocess.run( @@ -720,9 +753,7 @@ def add_context( context_path = os.path.join(self.temp_dir, f"context_{context_index}.txt") with open(context_path, "w") as f: f.write(context_payload) - self.execute_code( - f"with open(r'{context_path}', 'r') as f:\n {var_name} = f.read()" - ) + self.execute_code(f"with open(r'{context_path}', 'r') as f:\n {var_name} = f.read()") else: context_path = os.path.join(self.temp_dir, f"context_{context_index}.json") with open(context_path, "w") as f: @@ -798,7 +829,9 @@ def get_overhead_summary(self) -> dict: "packages_installed": [i["package"] for i in installs], "overhead_percentage": round( (total_overhead / (total_overhead + total_code_time)) * 100, 1 - ) if total_code_time > 0 else 0, + ) + if total_code_time > 0 + else 0, } def print_overhead_summary(self): diff --git a/tests/benchmarks/test_benchmarks.py b/tests/benchmarks/test_benchmarks.py index 75d96532..1ac1c4d0 100644 --- a/tests/benchmarks/test_benchmarks.py +++ b/tests/benchmarks/test_benchmarks.py @@ -299,7 +299,9 @@ def custom_inference(sample): ) # All samples should be correct (temp files weren't clobbered) - assert result.accuracy == 1.0, f"Some samples were clobbered: {[sr for sr in result.sample_results if not sr.is_correct]}" + assert result.accuracy == 1.0, ( + f"Some samples were clobbered: {[sr for sr in result.sample_results if not sr.is_correct]}" + ) # Verify multiple threads were used unique_threads = set(thread_ids.values()) @@ -398,11 +400,13 @@ def test_progress_callback_invoked(self): callback_invocations = [] def track_callback(completed, total, sample_result, stats): - callback_invocations.append({ - "completed": completed, - "total": total, - "accuracy": stats.accuracy, - }) + callback_invocations.append( + { + "completed": completed, + "total": total, + "accuracy": stats.accuracy, + } + ) benchmark = NIAHBenchmark(context_length=1000) runner = BenchmarkRunner( @@ -410,7 +414,7 @@ def track_callback(completed, total, sample_result, stats): progress_callback=track_callback, ) - result = runner.run( + runner.run( benchmark, method="custom", custom_fn=lambda s: s.expected_answer, diff --git a/tests/clients/test_huggingface.py b/tests/clients/test_huggingface.py index 7052ca53..f9951a0f 100644 --- a/tests/clients/test_huggingface.py +++ b/tests/clients/test_huggingface.py @@ -19,8 +19,10 @@ class TestHuggingFaceClientUnit(unittest.TestCase): def test_init_with_api_key(self): """Test client initialization with explicit API key.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key", model_name="test-model") self.assertEqual(client.model_name, "test-model") @@ -28,8 +30,10 @@ def test_init_with_base_url(self): """Test client initialization with base_url.""" url = "https://example.com" model = "test-model" - with patch("rlm.clients.huggingface.InferenceClient") as MockClient, \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient") as MockClient, + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key", model_name=model, base_url=url) self.assertEqual(client.model_name, model) self.assertEqual(client.base_url, url) @@ -39,45 +43,53 @@ def test_init_with_base_url(self): # Completion calls use model name mock_inst = MockClient.return_value - mock_inst.chat_completion.return_value.choices = [MagicMock(message=MagicMock(content="Hi"))] + mock_inst.chat_completion.return_value.choices = [ + MagicMock(message=MagicMock(content="Hi")) + ] client.completion("Hello") mock_inst.chat_completion.assert_called_with( - messages=[{"role": "user", "content": "Hello"}], - model=model + messages=[{"role": "user", "content": "Hello"}], model=model ) def test_init_with_url_model_name(self): """Test legacy behavior: model_name is URL, no base_url.""" url = "https://example.com" - with patch("rlm.clients.huggingface.InferenceClient") as MockClient, \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient") as MockClient, + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key", model_name=url) # Client init with URL MockClient.assert_called_with(token="test-key", model=url) mock_inst = MockClient.return_value - mock_inst.chat_completion.return_value.choices = [MagicMock(message=MagicMock(content="Hi"))] + mock_inst.chat_completion.return_value.choices = [ + MagicMock(message=MagicMock(content="Hi")) + ] client.completion("Hello") # Completion call uses None for model mock_inst.chat_completion.assert_called_with( - messages=[{"role": "user", "content": "Hello"}], - model=None + messages=[{"role": "user", "content": "Hello"}], model=None ) def test_init_default_model(self): """Test client uses default model name (None if not provided).""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") self.assertIsNone(client.model_name) def test_usage_tracking_initialization(self): """Test that usage tracking is properly initialized.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") self.assertEqual(client.model_call_counts, {}) self.assertEqual(client.model_input_tokens, {}) @@ -87,8 +99,10 @@ def test_usage_tracking_initialization(self): def test_get_usage_summary_empty(self): """Test usage summary when no calls have been made.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") summary = client.get_usage_summary() self.assertIsInstance(summary, UsageSummary) @@ -96,8 +110,10 @@ def test_get_usage_summary_empty(self): def test_get_last_usage(self): """Test last usage returns correct format.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") client.last_prompt_tokens = 100 client.last_completion_tokens = 50 @@ -109,16 +125,20 @@ def test_get_last_usage(self): def test_prepare_messages_string(self): """Test _prepare_messages with string input.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") messages = client._prepare_messages("Hello world") self.assertEqual(messages, [{"role": "user", "content": "Hello world"}]) def test_prepare_messages_list(self): """Test _prepare_messages with list of dicts input.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") input_messages = [{"role": "user", "content": "Hello"}] messages = client._prepare_messages(input_messages) @@ -126,24 +146,30 @@ def test_prepare_messages_list(self): def test_prepare_messages_invalid_type(self): """Test _prepare_messages raises InvalidPromptError for invalid input.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key") with self.assertRaises(InvalidPromptError): client._prepare_messages(12345) def test_completion_requires_model(self): """Test completion raises ModelRequiredError when no model specified.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key", model_name=None) with self.assertRaises(ModelRequiredError): client.completion("Hello") def test_completion_requires_model_with_base_url(self): """Test completion raises ModelRequiredError when using base_url without model.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"): + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + ): client = HuggingFaceClient(api_key="test-key", base_url="https://example.com") with self.assertRaises(ModelRequiredError): client.completion("Hello") @@ -154,9 +180,11 @@ class TestHuggingFaceListModels(unittest.TestCase): def test_list_models_returns_sorted_list(self): """Test list_models returns sorted list of model IDs.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"), \ - patch("rlm.clients.huggingface.list_models") as mock_list: + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + patch("rlm.clients.huggingface.list_models") as mock_list, + ): # Mock the list_models response mock_model1 = MagicMock() mock_model1.id = "meta-llama/Llama-2-7b" @@ -176,9 +204,11 @@ def test_list_models_returns_sorted_list(self): def test_list_models_returns_none_on_error(self): """Test list_models returns None when API fails.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"), \ - patch("rlm.clients.huggingface.list_models") as mock_list: + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + patch("rlm.clients.huggingface.list_models") as mock_list, + ): mock_list.side_effect = Exception("API Error") client = HuggingFaceClient(api_key="test-key") @@ -188,9 +218,11 @@ def test_list_models_returns_none_on_error(self): def test_alist_models_delegates_to_list_models(self): """Test alist_models uses the sync list_models implementation.""" - with patch("rlm.clients.huggingface.InferenceClient"), \ - patch("rlm.clients.huggingface.AsyncInferenceClient"), \ - patch("rlm.clients.huggingface.list_models") as mock_list: + with ( + patch("rlm.clients.huggingface.InferenceClient"), + patch("rlm.clients.huggingface.AsyncInferenceClient"), + patch("rlm.clients.huggingface.list_models") as mock_list, + ): mock_model = MagicMock() mock_model.id = "test-model" mock_list.return_value = [mock_model] @@ -198,6 +230,7 @@ def test_alist_models_delegates_to_list_models(self): client = HuggingFaceClient(api_key="test-key") import asyncio + result = asyncio.get_event_loop().run_until_complete(client.alist_models()) self.assertEqual(result, ["test-model"]) diff --git a/tests/environments/test_subprocess_repl.py b/tests/environments/test_subprocess_repl.py index ffd11593..3c1793fd 100644 --- a/tests/environments/test_subprocess_repl.py +++ b/tests/environments/test_subprocess_repl.py @@ -9,8 +9,7 @@ # Skip all tests if uv is not installed pytestmark = pytest.mark.skipif( - not shutil.which("uv"), - reason="uv is required for SubprocessREPL tests" + not shutil.which("uv"), reason="uv is required for SubprocessREPL tests" ) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 34eace33..4d1a108c 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -58,7 +58,9 @@ def test_client_errors_inherit_from_client_error(self): InvalidPromptError, ] for exc_cls in client_exceptions: - assert issubclass(exc_cls, ClientError), f"{exc_cls.__name__} should inherit from ClientError" + assert issubclass(exc_cls, ClientError), ( + f"{exc_cls.__name__} should inherit from ClientError" + ) def test_environment_errors_inherit_from_environment_error(self): """Environment-related exceptions should inherit from EnvironmentError.""" @@ -69,7 +71,9 @@ def test_environment_errors_inherit_from_environment_error(self): LMQueryError, ] for exc_cls in env_exceptions: - assert issubclass(exc_cls, EnvironmentError), f"{exc_cls.__name__} should inherit from EnvironmentError" + assert issubclass(exc_cls, EnvironmentError), ( + f"{exc_cls.__name__} should inherit from EnvironmentError" + ) def test_registry_errors_inherit_from_registry_error(self): """Registry-related exceptions should inherit from RegistryError.""" @@ -79,7 +83,9 @@ def test_registry_errors_inherit_from_registry_error(self): ValidationError, ] for exc_cls in registry_exceptions: - assert issubclass(exc_cls, RegistryError), f"{exc_cls.__name__} should inherit from RegistryError" + assert issubclass(exc_cls, RegistryError), ( + f"{exc_cls.__name__} should inherit from RegistryError" + ) class TestClientExceptions: @@ -110,7 +116,9 @@ def test_configuration_error_with_missing_field(self): def test_api_error_with_status_code(self): """APIError should store status code and response body.""" - exc = APIError("Rate limit exceeded", status_code=429, response_body={"error": "too many requests"}) + exc = APIError( + "Rate limit exceeded", status_code=429, response_body={"error": "too many requests"} + ) assert "Rate limit exceeded" in str(exc) assert exc.status_code == 429 assert exc.response_body == {"error": "too many requests"} @@ -127,7 +135,9 @@ def test_variable_not_found_error_message(self): def test_code_execution_error_with_details(self): """CodeExecutionError should store code and stderr.""" - exc = CodeExecutionError("Syntax error", code="print(", stderr="SyntaxError: unexpected EOF") + exc = CodeExecutionError( + "Syntax error", code="print(", stderr="SyntaxError: unexpected EOF" + ) assert "Syntax error" in str(exc) assert exc.code == "print(" assert exc.stderr == "SyntaxError: unexpected EOF" diff --git a/tests/test_registry.py b/tests/test_registry.py index 3a2588b0..2fcd279d 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -285,7 +285,15 @@ def test_client_config_has_env_vars_field(self): def test_all_backends_have_env_vars_defined(self): """All backends that need API keys have env_vars defined.""" # These backends should have api_key env var - api_key_backends = ["openai", "openrouter", "vercel", "anthropic", "gemini", "portkey", "huggingface"] + api_key_backends = [ + "openai", + "openrouter", + "vercel", + "anthropic", + "gemini", + "portkey", + "huggingface", + ] for backend in api_key_backends: config = CLIENT_REGISTRY[backend] assert "api_key" in config.env_vars, f"{backend} missing api_key env var" diff --git a/uv.lock b/uv.lock index 24f5b20f..335a6dd7 100644 --- a/uv.lock +++ b/uv.lock @@ -594,6 +594,15 @@ wheels = [ { 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.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, +] + [[package]] name = "google-auth" version = "2.45.0" @@ -669,6 +678,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, ] +[[package]] +name = "hf-xet" +version = "1.2.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" }, +] + [[package]] name = "hpack" version = "4.1.0" @@ -706,6 +744,27 @@ 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.3.2" +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 = "shellingham" }, + { name = "tqdm" }, + { name = "typer-slim" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/d6/02d1c505e1d3364230e5fa16d2b58c8f36a39c5efe8e99bc4d03d06fd0ca/huggingface_hub-1.3.2.tar.gz", hash = "sha256:15d7902e154f04174a0816d1e9594adcf15cdad57596920a5dc70fadb5d896c7", size = 624018, upload-time = "2026-01-14T13:57:39.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/1d/acd3ef8aabb7813c6ef2f91785d855583ac5cd7c3599e5c1a1a2ed1ec2e5/huggingface_hub-1.3.2-py3-none-any.whl", hash = "sha256:b552b9562a5532102a041fa31a6966bb9de95138fc7aa578bb3703198c25d1b6", size = 534504, upload-time = "2026-01-14T13:57:37.555Z" }, +] + [[package]] name = "hyperframe" version = "6.1.0" @@ -1495,6 +1554,7 @@ source = { editable = "." } dependencies = [ { name = "anthropic" }, { name = "google-genai" }, + { name = "huggingface-hub" }, { name = "openai" }, { name = "portkey-ai" }, { name = "pytest" }, @@ -1531,6 +1591,7 @@ requires-dist = [ { name = "dill", marker = "extra == 'modal'", specifier = ">=0.3.7" }, { name = "dill", marker = "extra == 'prime'", specifier = ">=0.3.7" }, { name = "google-genai", specifier = ">=1.56.0" }, + { name = "huggingface-hub", specifier = ">=0.20.0" }, { name = "modal", marker = "extra == 'modal'", specifier = ">=0.73.0" }, { name = "openai", specifier = ">=2.14.0" }, { name = "portkey-ai", specifier = ">=2.1.0" }, @@ -1741,6 +1802,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/e4/5ebc1899d31d2b1601b32d21cfb4bba022ae6fce323d365f0448031b1660/typer-0.21.0-py3-none-any.whl", hash = "sha256:c79c01ca6b30af9fd48284058a7056ba0d3bf5cf10d0ff3d0c5b11b68c258ac6", size = 47109, upload-time = "2025-12-25T09:54:51.918Z" }, ] +[[package]] +name = "typer-slim" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/d4/064570dec6358aa9049d4708e4a10407d74c99258f8b2136bb8702303f1a/typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd", size = 110478, upload-time = "2026-01-06T11:21:11.176Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/0a/4aca634faf693e33004796b6cee0ae2e1dba375a800c16ab8d3eff4bb800/typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d", size = 47444, upload-time = "2026-01-06T11:21:12.441Z" }, +] + [[package]] name = "types-certifi" version = "2021.10.8.3"