fix(agent): validate non-empty retry delays; pin exhaustion semantics
核心算法 #10(Agent Loop):Codex 质量审查跟进,仅加固防御与 可观测,不改变循环语义。 - __init__ 校验 step_retry_delays 非空,空序列直接抛 ValueError (P5 fail-fast,避免首次可重试异常时 IndexError 掩盖原始 LLM 异常、违背方法契约) - 补耗尽语义窄测试(耗尽后原样抛出最后一次原始异常)与空序列 构造校验测试 - run() 最终失败日志补异常类型名,便于回溯归因
This commit is contained in:
@@ -363,3 +363,23 @@ class TestStepLevelRetry:
|
||||
assert result.stop_reason == "error"
|
||||
sleep_mock.assert_not_awaited()
|
||||
assert loop._llm.chat.await_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exhaustion_raises_last_original_exception(self, monkeypatch) -> None:
|
||||
"""耗尽语义窄测试:_call_llm_with_step_retry 耗尽后原样抛出最后一次异常。"""
|
||||
|
||||
async def _fake_sleep(seconds: float) -> None:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr("core.agent.loop.asyncio.sleep", _fake_sleep)
|
||||
last_error = TimeoutError("t3-last")
|
||||
loop = self._make_loop([TimeoutError("t1"), TimeoutError("t2"), last_error])
|
||||
token_usage = {"prompt_tokens": 0, "completion_tokens": 0}
|
||||
with pytest.raises(TimeoutError) as exc_info:
|
||||
await loop._call_llm_with_step_retry([], token_usage, session_id=None)
|
||||
assert exc_info.value is last_error
|
||||
|
||||
def test_empty_retry_delays_rejected_at_init(self) -> None:
|
||||
"""空 step_retry_delays 在构造时 fail-fast 抛 ValueError。"""
|
||||
with pytest.raises(ValueError, match="step_retry_delays"):
|
||||
AgentLoop(llm=AsyncMock(), max_steps=10, step_retry_delays=())
|
||||
|
||||
Reference in New Issue
Block a user