diff --git a/poetry.lock b/poetry.lock index df7653367..8310e1ec1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1060,6 +1060,17 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "deprecated" version = "1.2.14" @@ -1250,7 +1261,7 @@ standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] name = "ffmpy" version = "0.4.0" description = "A simple Python wrapper for FFmpeg" -optional = true +optional = false python-versions = "<4.0.0,>=3.8.1" files = [ {file = "ffmpy-0.4.0-py3-none-any.whl", hash = "sha256:39c0f20c5b465e7f8d29a5191f3a7d7675a8c546d9d985de8921151cd9b59e14"}, @@ -3850,8 +3861,6 @@ files = [ {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"}, {file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"}, {file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"}, - {file = "orjson-3.10.6-cp313-none-win32.whl", hash = "sha256:efdf2c5cde290ae6b83095f03119bdc00303d7a03b42b16c54517baa3c4ca3d0"}, - {file = "orjson-3.10.6-cp313-none-win_amd64.whl", hash = "sha256:8e190fe7888e2e4392f52cafb9626113ba135ef53aacc65cd13109eb9746c43e"}, {file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"}, {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"}, {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"}, @@ -4970,6 +4979,20 @@ requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] +[[package]] +name = "retry-async" +version = "0.1.4" +description = "" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "retry_async-0.1.4-py3-none-any.whl", hash = "sha256:21b383c7bc52013478337b894f476c9f106485cfeeb5d449abe5f745be2da219"}, + {file = "retry_async-0.1.4.tar.gz", hash = "sha256:8414d69b20920a1d700de34b68c0f972fa36a0158450a6f6abc5b45a241ac6b6"}, +] + +[package.dependencies] +decorator = ">=5.1.1,<6.0.0" + [[package]] name = "rich" version = "13.7.1" @@ -6691,4 +6714,4 @@ vector-stores-qdrant = ["llama-index-vector-stores-qdrant"] [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.12" -content-hash = "2eaa56bf185723ad028f5221675f1ee070bc70ba7d606ebe28dcfe276a3c9dca" +content-hash = "45264d80672084e35ca0ea11b368a29001a3b9003822bddc67fb18489a8fe519" diff --git a/private_gpt/utils/ollama.py b/private_gpt/utils/ollama.py index 9c75a875a..da9107bff 100644 --- a/private_gpt/utils/ollama.py +++ b/private_gpt/utils/ollama.py @@ -3,10 +3,13 @@ from collections.abc import Iterator, Mapping from typing import Any +from httpx import ConnectError from tqdm import tqdm # type: ignore +from private_gpt.utils.retry import retry + try: - from ollama import Client # type: ignore + from ollama import Client, ResponseError # type: ignore except ImportError as e: raise ImportError( "Ollama dependencies not found, install with `poetry install --extras llms-ollama or embeddings-ollama`" @@ -14,13 +17,25 @@ logger = logging.getLogger(__name__) +_MAX_RETRIES = 5 +_JITTER = (3.0, 10.0) + +@retry( + is_async=False, + exceptions=(ConnectError, ResponseError), + tries=_MAX_RETRIES, + jitter=_JITTER, + logger=logger, +) def check_connection(client: Client) -> bool: try: client.list() return True + except (ConnectError, ResponseError) as e: + raise e except Exception as e: - logger.error(f"Failed to connect to Ollama: {e!s}") + logger.error(f"Failed to connect to Ollama: {type(e).__name__}: {e!s}") return False diff --git a/private_gpt/utils/retry.py b/private_gpt/utils/retry.py new file mode 100644 index 000000000..614b6803d --- /dev/null +++ b/private_gpt/utils/retry.py @@ -0,0 +1,31 @@ +import logging +from collections.abc import Callable +from typing import Any + +from retry_async import retry as retry_untyped # type: ignore + +retry_logger = logging.getLogger(__name__) + + +def retry( + exceptions: Any = Exception, + *, + is_async: bool = False, + tries: int = -1, + delay: float = 0, + max_delay: float | None = None, + backoff: float = 1, + jitter: float | tuple[float, float] = 0, + logger: logging.Logger = retry_logger, +) -> Callable[..., Any]: + wrapped = retry_untyped( + exceptions=exceptions, + is_async=is_async, + tries=tries, + delay=delay, + max_delay=max_delay, + backoff=backoff, + jitter=jitter, + logger=logger, + ) + return wrapped # type: ignore diff --git a/pyproject.toml b/pyproject.toml index 17a7c6985..da9fab80a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ ollama = {version ="^0.3.0", optional = true} # Optional HF Transformers einops = {version = "^0.8.0", optional = true} +retry-async = "^0.1.4" [tool.poetry.extras] ui = ["gradio", "ffmpy"]