diff --git a/docs/07_tools.md b/docs/07_tools.md index ffb8552c..32b04cc0 100644 --- a/docs/07_tools.md +++ b/docs/07_tools.md @@ -181,6 +181,18 @@ A tool’s __call__ method may return: - None - a list or tuple containing any of the above +**Image size limit:** When a tool returns a `PIL.Image.Image`, it is the tool’s responsibility to ensure the image does not exceed **2000×2000 px** (longest side ≤ 2000 px). The Claude API enforces a 2000×2000 px per-image limit when more than 20 images are sent in a single request, which is common in agentic loops. Use `downscale_image()` from `askui.utils.image_utils` to downscale images that may be too large: + +```python +from PIL import Image +from askui.utils.image_utils import downscale_image + +image: Image.Image = ... # your image +image = downscale_image(image, max_dimension=2000) +``` + +This preserves the original aspect ratio and only downscales images whose longest side exceeds the limit. + ### Complete Example Here’s a greeting tool that demonstrates all the key concepts: diff --git a/src/askui/utils/image_utils.py b/src/askui/utils/image_utils.py index 19a5f92c..501f1fbb 100644 --- a/src/askui/utils/image_utils.py +++ b/src/askui/utils/image_utils.py @@ -227,6 +227,33 @@ def scale_image_to_fit( return _center_image_in_background(scaled_image, target_size) +def downscale_image( + image: Image.Image, + max_dimension: int = 2000, +) -> Image.Image: + """Downscale an image so its longest side does not exceed `max_dimension`. + + Preserves the original aspect ratio. Images that are already + within the limit are returned unchanged. + + Args: + image (Image.Image): The PIL Image to downscale. + max_dimension (int, optional): Maximum allowed size for the longest side. Defaults to `2000`. + + Returns: + Image.Image: The downscaled image, or the original if no scaling was needed. + """ + longest_side = max(image.width, image.height) + if longest_side <= max_dimension: + return image + scale_factor = max_dimension / longest_side + new_size = ( + int(image.width * scale_factor), + int(image.height * scale_factor), + ) + return image.resize(new_size, Image.Resampling.LANCZOS) + + def _scale_coordinates( coordinates: tuple[int, int], offset: tuple[int, int],