fix(retry): strip images in-place to prevent repeated error-retry cycles

When a non-transient LLM error occurs with image content, the retry
mechanism strips images from a copy but never updates the original
conversation history. Subsequent iterations rebuild context from the
unmodified history, causing the same error-retry cycle to repeat
every iteration until max_iterations is reached.

Add _strip_image_content_inplace() that mutates the original message
content lists in-place after a successful no-image retry, so callers
sharing those references (e.g. the runner's conversation history)
also see the stripped version.
This commit is contained in:
yanghan-cyber
2026-04-12 20:10:06 +08:00
committed by Xubin Ren
parent 7a7f5c9689
commit b261201985
2 changed files with 30 additions and 4 deletions
+4 -3
View File
@@ -1,4 +1,5 @@
import asyncio
import copy
import pytest
@@ -152,7 +153,7 @@ async def test_non_transient_error_with_images_retries_without_images() -> None:
LLMResponse(content="ok, no image"),
])
response = await provider.chat_with_retry(messages=_IMAGE_MSG)
response = await provider.chat_with_retry(messages=copy.deepcopy(_IMAGE_MSG))
assert response.content == "ok, no image"
assert provider.calls == 2
@@ -187,7 +188,7 @@ async def test_image_fallback_returns_error_on_second_failure() -> None:
LLMResponse(content="still failing", finish_reason="error"),
])
response = await provider.chat_with_retry(messages=_IMAGE_MSG)
response = await provider.chat_with_retry(messages=copy.deepcopy(_IMAGE_MSG))
assert provider.calls == 2
assert response.content == "still failing"
@@ -202,7 +203,7 @@ async def test_image_fallback_without_meta_uses_default_placeholder() -> None:
LLMResponse(content="ok"),
])
response = await provider.chat_with_retry(messages=_IMAGE_MSG_NO_META)
response = await provider.chat_with_retry(messages=copy.deepcopy(_IMAGE_MSG_NO_META))
assert response.content == "ok"
assert provider.calls == 2