diff --git a/ci/lint/pydoclint-baseline.txt b/ci/lint/pydoclint-baseline.txt index c25a6491b788..743db400dfa2 100644 --- a/ci/lint/pydoclint-baseline.txt +++ b/ci/lint/pydoclint-baseline.txt @@ -1612,8 +1612,6 @@ python/ray/llm/_internal/serve/configs/prompt_formats.py DOC201: Method `Image.check_image_url` does not have a return section in docstring -------------------- python/ray/llm/_internal/serve/deployments/llm/llm_server.py - DOC101: Method `LLMServer.__init__`: Docstring contains fewer arguments than in function signature. - DOC103: Method `LLMServer.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [engine_cls: Optional[Type[LLMEngine]], image_retriever_cls: Optional[Type[ImageRetriever]], model_downloader: Optional[LoraModelLoader]]. DOC402: Method `LLMServer.embeddings` has "yield" statements, but the docstring does not have a "Yields" section DOC404: Method `LLMServer.embeddings` yield type(s) in docstring not consistent with the return annotation. Return annotation exists, but docstring "yields" section does not exist or has 0 type(s). -------------------- diff --git a/python/ray/llm/_internal/serve/deployments/llm/image_retriever.py b/python/ray/llm/_internal/serve/deployments/llm/image_retriever.py deleted file mode 100644 index 6b5c1e11a660..000000000000 --- a/python/ray/llm/_internal/serve/deployments/llm/image_retriever.py +++ /dev/null @@ -1,60 +0,0 @@ -import asyncio -import base64 -import io -from typing import TYPE_CHECKING - -import aiohttp - -from ray.llm._internal.common.utils.import_utils import try_import - -if TYPE_CHECKING: - from PIL.Image import Image - -PIL = try_import("PIL") - -# TODO(xwjiang): Make this configurable in Launch Darkly. -TIMEOUT = 10 # seconds -RETRIES = 3 # Number of retries on timeout - - -class ImageRetriever: - """Retrieves images.""" - - async def get(self, url: str) -> "Image": - """Retrieves an image.""" - if url.startswith("data"): - base64_encoded_str = url.split(",")[1] - try: - image_data = base64.b64decode(base64_encoded_str) - except base64.binascii.Error as e: - raise ValueError("Failed to decode base64 string") from e - else: - for attempt in range(RETRIES): - try: - async with aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=TIMEOUT) - ) as session: - async with session.get(url) as resp: - if resp.status == 200: - image_data = await resp.read() - break - else: - raise RuntimeError( - f"Failed to fetch image from {url}, received status code: {resp.status}" - ) - except asyncio.TimeoutError: - if attempt < RETRIES - 1: - await asyncio.sleep(2**attempt) - continue - else: - raise RuntimeError( - "Request timed out after several retries" - ) from None - except aiohttp.ClientError as e: - raise RuntimeError("Network error occurred") from e - - try: - image = PIL.Image.open(io.BytesIO(image_data)) - return image - except PIL.UnidentifiedImageError as e: - raise ValueError("Failed to identify image") from e diff --git a/python/ray/llm/_internal/serve/deployments/llm/llm_server.py b/python/ray/llm/_internal/serve/deployments/llm/llm_server.py index d1105db5afa8..226d18ae92af 100644 --- a/python/ray/llm/_internal/serve/deployments/llm/llm_server.py +++ b/python/ray/llm/_internal/serve/deployments/llm/llm_server.py @@ -45,7 +45,6 @@ LLMConfig, LLMRawResponse, ) -from ray.llm._internal.serve.deployments.llm.image_retriever import ImageRetriever from ray.llm._internal.serve.deployments.llm.llm_engine import LLMEngine from ray.llm._internal.serve.deployments.llm.multiplex.lora_model_loader import ( LoraModelLoader, @@ -409,14 +408,12 @@ async def process_completions( class LLMServer(_LLMServerBase): _default_engine_cls = VLLMEngine - _default_image_retriever_cls = ImageRetriever async def __init__( self, llm_config: LLMConfig, *, engine_cls: Optional[Type[LLMEngine]] = None, - image_retriever_cls: Optional[Type[ImageRetriever]] = None, model_downloader: Optional[LoraModelLoader] = None, ): """Constructor of LLMServer. @@ -426,14 +423,10 @@ async def __init__( Args: llm_config: LLMConfig for the model. - - Keyword Args: - engine_cls: Dependency injection for the vllm engine class. Defaults to - `VLLMEngine`. - image_retriever_cls: Dependency injection for the image retriever class. - Defaults to `ImageRetriever`. - model_downloader: Dependency injection for the model downloader object. - Defaults to be initialized with `LoraModelLoader`. + engine_cls: Dependency injection for the vllm engine class. + Defaults to `VLLMEngine`. + model_downloader: Dependency injection for the model downloader + object. Defaults to be initialized with `LoraModelLoader`. """ await super().__init__(llm_config) @@ -443,14 +436,6 @@ async def __init__( self.engine = self._engine_cls(self._llm_config) await asyncio.wait_for(self._start_engine(), timeout=ENGINE_START_TIMEOUT_S) - # TODO (Kourosh): I think we can completely remove image retriever. - # It was missed to get removed. - self.image_retriever = ( - image_retriever_cls() - if image_retriever_cls - else self._default_image_retriever_cls() - ) - multiplex_config = self._llm_config.multiplex_config() if model_downloader: self.model_downloader = model_downloader diff --git a/python/ray/llm/tests/serve/cpu/deployments/llm/multiplex/test_lora_deployment_base_client.py b/python/ray/llm/tests/serve/cpu/deployments/llm/multiplex/test_lora_deployment_base_client.py index 3e11494f2522..53b10d1dcbcb 100644 --- a/python/ray/llm/tests/serve/cpu/deployments/llm/multiplex/test_lora_deployment_base_client.py +++ b/python/ray/llm/tests/serve/cpu/deployments/llm/multiplex/test_lora_deployment_base_client.py @@ -15,7 +15,6 @@ from ray.llm._internal.serve.deployments.routers.router import ( LLMRouter, ) -from ray.llm.tests.serve.mocks.fake_image_retriever import FakeImageRetriever from ray.llm.tests.serve.mocks.mock_vllm_engine import MockEchoVLLMEngine from ray.serve.handle import DeploymentHandle @@ -68,7 +67,6 @@ def get_mocked_llm_deployments(llm_configs) -> List[DeploymentHandle]: deployment.bind( llm_config=llm_config, engine_cls=MockEchoVLLMEngine, - image_retriever_cls=FakeImageRetriever, ) ) return llm_deployments diff --git a/python/ray/llm/tests/serve/cpu/deployments/llm/test_image_retriever.py b/python/ray/llm/tests/serve/cpu/deployments/llm/test_image_retriever.py deleted file mode 100644 index 5cf3834ee711..000000000000 --- a/python/ray/llm/tests/serve/cpu/deployments/llm/test_image_retriever.py +++ /dev/null @@ -1,65 +0,0 @@ -import base64 -import io -import sys -from unittest.mock import AsyncMock, patch - -import pytest -from PIL import Image - -from ray.llm._internal.serve.deployments.llm.image_retriever import ImageRetriever - - -def create_dummy_image_bytes(): - image = Image.new("RGB", (1300, 876), color="red") - img_byte_arr = io.BytesIO() - image.save(img_byte_arr, format="PNG") - return img_byte_arr.getvalue() - - -def get_mock_resp_ctx(): - image_bytes = create_dummy_image_bytes() - mock_response = AsyncMock() - mock_response.status = 200 - mock_response.read = AsyncMock(return_value=image_bytes) - - mock_resp_ctx = AsyncMock() - mock_resp_ctx.__aenter__ = AsyncMock(return_value=mock_response) - - return mock_resp_ctx - - -@pytest.mark.asyncio -async def test_image_processor_with_base64(): - image_bytes = create_dummy_image_bytes() - base64_encoded_str = base64.b64encode(image_bytes).decode("utf-8") - data_url = f"data:image/png;base64,{base64_encoded_str}" - - retriever = ImageRetriever() - image = await retriever.get(data_url) - - assert isinstance(image, Image.Image) - - -@pytest.mark.asyncio -async def test_image_processor_with_bad_base64_enc(): - data_url = "data:image/png;base64,invalid_base64_string" - - retriever = ImageRetriever() - # Act and Assert - with pytest.raises(ValueError, match="Failed to decode base64 string"): - await retriever.get(data_url) - - -@pytest.mark.asyncio -async def test_image_processor_with_http_url(): - mock_resp_ctx = get_mock_resp_ctx() - - retriever = ImageRetriever() - with patch("aiohttp.ClientSession.get", return_value=mock_resp_ctx): - image = await retriever.get("http://dummyurl.com/image.png") - - assert isinstance(image, Image.Image) - - -if __name__ == "__main__": - sys.exit(pytest.main(["-v", __file__])) diff --git a/python/ray/llm/tests/serve/mocks/fake_image_retriever.py b/python/ray/llm/tests/serve/mocks/fake_image_retriever.py deleted file mode 100644 index 5924f98c336c..000000000000 --- a/python/ray/llm/tests/serve/mocks/fake_image_retriever.py +++ /dev/null @@ -1,15 +0,0 @@ -import numpy as np -from PIL import Image - -from ray.llm._internal.serve.deployments.llm.image_retriever import ImageRetriever - - -class FakeImageRetriever(ImageRetriever): - def __init__(self): - pass - - async def get(self, url: str) -> Image.Image: - height, width = 256, 256 - random_image = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8) - - return Image.fromarray(random_image)