fix: image fetching timeouts [LET-6700] (#8140)

fix: image fetching timeouts
This commit is contained in:
cthomas
2025-12-29 13:30:34 -08:00
committed by Caren Thomas
parent 21df642a43
commit 39dc1d9736

View File

@@ -12,23 +12,52 @@ from letta.schemas.letta_message_content import Base64Image, ImageContent, Image
from letta.schemas.message import Message, MessageCreate
async def _fetch_image_from_url(url: str) -> tuple[bytes, str | None]:
async def _fetch_image_from_url(url: str, max_retries: int = 1, timeout_seconds: float = 5.0) -> tuple[bytes, str | None]:
"""
Async helper to fetch image from URL without blocking the event loop.
Retries once on timeout to handle transient network issues.
Args:
url: URL of the image to fetch
max_retries: Number of retry attempts (default: 1)
timeout_seconds: Total timeout in seconds (default: 5.0)
Returns:
Tuple of (image_bytes, media_type)
Raises:
LettaImageFetchError: If image fetch fails after all retries
"""
timeout = httpx.Timeout(15.0, connect=5.0)
# Connect timeout is half of total timeout, capped at 3 seconds
connect_timeout = min(timeout_seconds / 2, 3.0)
timeout = httpx.Timeout(timeout_seconds, connect=connect_timeout)
headers = {"User-Agent": f"Letta/{__version__}"}
try:
async with httpx.AsyncClient(timeout=timeout, headers=headers) as client:
image_response = await client.get(url, follow_redirects=True)
image_response.raise_for_status()
image_bytes = image_response.content
image_media_type = image_response.headers.get("content-type")
return image_bytes, image_media_type
except (httpx.RemoteProtocolError, httpx.TimeoutException, httpx.HTTPStatusError) as e:
raise LettaImageFetchError(url=url, reason=str(e))
except Exception as e:
raise LettaImageFetchError(url=url, reason=f"Unexpected error: {e}")
last_exception = None
for attempt in range(max_retries + 1):
try:
async with httpx.AsyncClient(timeout=timeout, headers=headers) as client:
image_response = await client.get(url, follow_redirects=True)
image_response.raise_for_status()
image_bytes = image_response.content
image_media_type = image_response.headers.get("content-type")
return image_bytes, image_media_type
except httpx.TimeoutException as e:
last_exception = e
if attempt < max_retries:
# Brief delay before retry
await asyncio.sleep(0.5)
continue
# Final attempt failed
raise LettaImageFetchError(url=url, reason=f"Timeout after {max_retries + 1} attempts: {e}")
except (httpx.RemoteProtocolError, httpx.HTTPStatusError) as e:
# Don't retry on protocol errors or HTTP errors (4xx, 5xx)
raise LettaImageFetchError(url=url, reason=str(e))
except Exception as e:
raise LettaImageFetchError(url=url, reason=f"Unexpected error: {e}")
# Should never reach here, but just in case
raise LettaImageFetchError(url=url, reason=f"Failed after {max_retries + 1} attempts: {last_exception}")
async def convert_message_creates_to_messages(