fix(llm): retry intermittent OpenRouter prompt rejections

This commit is contained in:
oyasumi 2026-08-19 21:23:25 +00:00
parent 94a2586aaa
commit db1e6095b6
2 changed files with 72 additions and 0 deletions

View file

@ -108,8 +108,12 @@ async def _compact_session(
_MAX_TRANSIENT_MODEL_RETRIES = 5
_MAX_OPENROUTER_PROMPT_POLICY_RETRIES = 3
_TRANSIENT_MODEL_RETRY_BASE_DELAY_S = 2.0
_TRANSIENT_MODEL_RETRY_MAX_DELAY_S = 90.0
_OPENROUTER_PROMPT_POLICY_REJECTION = (
"invalid prompt: your prompt was flagged as potentially violating our usage policy"
)
def _model_error_status_code(exc: BaseException) -> int | None:
@ -117,6 +121,11 @@ def _model_error_status_code(exc: BaseException) -> int | None:
return code if isinstance(code, int) else None
def _is_openrouter_prompt_policy_rejection(exc: BaseException) -> bool:
error_text = str(exc).lower()
return "openrouter" in error_text and _OPENROUTER_PROMPT_POLICY_REJECTION in error_text
def _is_transient_model_error(exc: BaseException) -> bool:
if codex.is_content_guardrail_error(exc):
return False
@ -643,6 +652,7 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
image_strips = 0
compactions = 0
model_retries = 0
prompt_policy_retries = 0
while True:
stream: Any = None
pre_run_items: list[Any] = []
@ -757,6 +767,25 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
)
input_data = []
continue
if (
prompt_policy_retries < _MAX_OPENROUTER_PROMPT_POLICY_RETRIES
and _is_openrouter_prompt_policy_rejection(exc)
):
prompt_policy_retries += 1
delay = _transient_model_retry_delay(prompt_policy_retries)
logger.warning(
"intermittent OpenRouter prompt-policy rejection for %s; replaying "
"unchanged turn (attempt %d/%d, backoff %.1fs): %r",
agent_id,
prompt_policy_retries,
_MAX_OPENROUTER_PROMPT_POLICY_RETRIES,
delay,
exc,
)
await asyncio.sleep(delay)
if session is not None:
input_data = []
continue
if model_retries < _MAX_TRANSIENT_MODEL_RETRIES and _is_transient_model_error(exc):
model_retries += 1
delay = _transient_model_retry_delay(model_retries)

View file

@ -36,6 +36,15 @@ def _status_error(status: int) -> APIStatusError:
)
def _openrouter_prompt_policy_rejection() -> BadRequestError:
return BadRequestError(
"OpenrouterException - Message: Invalid prompt: your prompt was flagged as "
"potentially violating our usage policy. Please try again with a different prompt",
response=httpx.Response(400, request=_request()),
body=None,
)
def test_midstream_api_error_is_transient() -> None:
assert execution._is_transient_model_error(_midstream_api_error()) is True
@ -79,6 +88,12 @@ def test_content_guardrail_is_not_retried() -> None:
assert execution._is_transient_model_error(guardrail) is False
def test_openrouter_prompt_policy_rejection_has_dedicated_classification() -> None:
rejection = _openrouter_prompt_policy_rejection()
assert execution._is_openrouter_prompt_policy_rejection(rejection) is True
assert execution._is_transient_model_error(rejection) is False
def test_client_errors_are_not_transient() -> None:
bad_request = BadRequestError(
"bad", response=httpx.Response(400, request=_request()), body=None
@ -150,6 +165,34 @@ async def test_run_cycle_retries_transient_midstream_error(
assert attempts == 2
@pytest.mark.asyncio
async def test_run_cycle_retries_openrouter_prompt_policy_rejection_three_times(
monkeypatch: pytest.MonkeyPatch,
) -> None:
streams = [
_FakeStream(exc=_openrouter_prompt_policy_rejection())
for _ in range(execution._MAX_OPENROUTER_PROMPT_POLICY_RETRIES)
]
streams.append(_FakeStream())
result, attempts, _coordinator = await _run_once(monkeypatch, streams)
assert result is streams[-1]
assert attempts == 4
@pytest.mark.asyncio
async def test_run_cycle_gives_up_after_openrouter_prompt_policy_retry_limit(
monkeypatch: pytest.MonkeyPatch,
) -> None:
streams = [
_FakeStream(exc=_openrouter_prompt_policy_rejection())
for _ in range(execution._MAX_OPENROUTER_PROMPT_POLICY_RETRIES + 1)
]
with pytest.raises(BadRequestError):
await _run_once(monkeypatch, streams)
@pytest.mark.asyncio
async def test_run_cycle_gives_up_after_max_retries(
monkeypatch: pytest.MonkeyPatch,