Skip to content

Commit a573f78

Browse files
author
phernandez
committed
try to optimize image size for mcp tool response
1 parent ab36e7e commit a573f78

1 file changed

Lines changed: 73 additions & 35 deletions

File tree

src/basic_memory/mcp/tools/resource.py

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@
1010
import io
1111
from PIL import Image as PILImage
1212

13+
def calculate_target_params(content_length):
14+
"""Calculate initial quality and size based on input file size"""
15+
target_size = 350000 # Reduced target for more safety margin
16+
ratio = content_length / target_size
17+
18+
logger.debug("Calculating target parameters",
19+
content_length=content_length,
20+
ratio=ratio,
21+
target_size=target_size)
22+
23+
if ratio > 4:
24+
# Very large images - start very aggressive
25+
return 50, 600 # Lower initial quality and size
26+
elif ratio > 2:
27+
return 60, 800
28+
else:
29+
return 70, 1000
30+
1331
def resize_image(img, max_size):
1432
"""Resize image maintaining aspect ratio"""
1533
original_dimensions = {"width": img.width, "height": img.height}
@@ -26,29 +44,37 @@ def resize_image(img, max_size):
2644
logger.debug("No resize needed", dimensions=original_dimensions)
2745
return img
2846

29-
def optimize_image(img, max_output_bytes=500000): # 500KB limit
30-
"""Iteratively optimize image until it's under max_output_bytes"""
31-
logger.debug("Starting optimization",
32-
dimensions={"width": img.width, "height": img.height},
33-
mode=img.mode,
34-
max_bytes=max_output_bytes)
47+
def optimize_image(img, content_length, max_output_bytes=350000):
48+
"""Iteratively optimize image with aggressive size reduction"""
49+
stats = {
50+
"dimensions": {"width": img.width, "height": img.height},
51+
"mode": img.mode,
52+
"estimated_memory": (img.width * img.height * len(img.getbands()))
53+
}
3554

36-
# Start with higher quality for better color preservation
37-
quality = 60
55+
initial_quality, initial_size = calculate_target_params(content_length)
3856

39-
# Make initial size relative to input dimensions
40-
initial_size = min(800, max(img.width, img.height))
57+
logger.debug("Starting optimization",
58+
image_stats=stats,
59+
content_length=content_length,
60+
initial_quality=initial_quality,
61+
initial_size=initial_size,
62+
max_output_bytes=max_output_bytes)
63+
64+
quality = initial_quality
4165
size = initial_size
4266

43-
original_mode = img.mode
67+
# Convert to RGB if needed
4468
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
45-
# Keep original mode info for logging
4669
img = img.convert('RGB')
47-
logger.debug("Converted color mode",
48-
from_mode=original_mode,
49-
to_mode='RGB')
70+
logger.debug("Converted to RGB mode")
71+
72+
iteration = 0
73+
min_size = 300 # Absolute minimum size
74+
min_quality = 20 # Absolute minimum quality
5075

5176
while True:
77+
iteration += 1
5278
buf = io.BytesIO()
5379
resized = resize_image(img, size)
5480

@@ -59,37 +85,42 @@ def optimize_image(img, max_output_bytes=500000): # 500KB limit
5985
subsampling='4:2:0')
6086

6187
output_size = buf.getbuffer().nbytes
88+
reduction_ratio = output_size / content_length
89+
6290
logger.debug("Optimization attempt",
91+
iteration=iteration,
6392
quality=quality,
6493
size=size,
6594
output_bytes=output_size,
66-
target_bytes=max_output_bytes)
95+
target_bytes=max_output_bytes,
96+
reduction_ratio=f"{reduction_ratio:.2f}")
6797

6898
if output_size < max_output_bytes:
69-
compression_ratio = output_size / max_output_bytes
7099
logger.info("Image optimization complete",
71100
final_size=output_size,
72101
quality=quality,
73102
dimensions={"width": resized.width, "height": resized.height},
74-
compression_ratio=compression_ratio)
103+
reduction_ratio=f"{reduction_ratio:.2f}")
75104
return buf.getvalue()
76-
77-
# More gradual quality reduction for better color preservation
78-
if quality > 30:
79-
quality_step = 5 if quality > 50 else 10
80-
quality -= quality_step
81-
logger.debug("Reducing quality",
82-
new_quality=quality,
83-
step=quality_step)
84-
# Smaller size reduction steps
85-
elif size > 300:
86-
size_step = 25 if size > 600 else 50
87-
size -= size_step
88-
logger.debug("Reducing size",
89-
new_size=size,
90-
step=size_step)
105+
106+
# Very aggressive reduction for large files
107+
if content_length > 2000000: # 2MB+
108+
quality = max(min_quality, quality - 20)
109+
size = max(min_size, int(size * 0.6))
110+
elif content_length > 1000000: # 1MB+
111+
quality = max(min_quality, quality - 15)
112+
size = max(min_size, int(size * 0.7))
91113
else:
92-
logger.warning("Reached minimum optimization parameters",
114+
quality = max(min_quality, quality - 10)
115+
size = max(min_size, int(size * 0.8))
116+
117+
logger.debug("Reducing parameters",
118+
new_quality=quality,
119+
new_size=size)
120+
121+
# If we've hit minimum values and still too big
122+
if quality <= min_quality and size <= min_size:
123+
logger.warning("Reached minimum parameters",
93124
final_size=output_size,
94125
over_limit_by=output_size - max_output_bytes)
95126
return buf.getvalue()
@@ -123,7 +154,7 @@ async def read_resource(path: str) -> dict:
123154
elif content_type.startswith("image/"):
124155
logger.debug("Processing image")
125156
img = PILImage.open(io.BytesIO(response.content))
126-
img_bytes = optimize_image(img)
157+
img_bytes = optimize_image(img, content_length)
127158

128159
return {
129160
"type": "image",
@@ -137,6 +168,13 @@ async def read_resource(path: str) -> dict:
137168
# Handle other file types
138169
else:
139170
logger.debug("Processing binary resource")
171+
if content_length > 350000:
172+
logger.warning("Document too large for response",
173+
size=content_length)
174+
return {
175+
"type": "error",
176+
"error": f"Document size {content_length} bytes exceeds maximum allowed size"
177+
}
140178
return {
141179
"type": "document",
142180
"source": {

0 commit comments

Comments
 (0)