fix(hypothesis): catch EmptyCompletionError dentro propose() invece di propagare
Run phase2-5-qwen25-prompt-mut-001 fallito perché qwen-2.5-72b ha emesso empty completion ripetutamente; le 3 retry tenacity in LLMClient.complete si sono esaurite e l'exception è bollata fino a run_phase1, marcando l'intero run come failed (perso ~$0.017 di tier C). Fix: HypothesisAgent.propose() ora cattura EmptyCompletionError nel loop max_attempts trattandola come parse-fail "empty_completion" e ritentando con max_retries+1 tentativi (cumulativo: max_retries tenacity × max_attempts loop esterno, default 3×3 = 9 retry effettive). Se TUTTI i tentativi falliscono con empty, ritorna proposal con strategy=None e parse_error="empty_completion: ...", lasciando l'orchestrator continuare con quel genome marcato come "fitness=0" senza crash totale del run. +2 test: success dopo 1 empty + retry; failure totale dopo 3 empty consecutive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import json
|
||||
|
||||
from multi_swarm.agents.hypothesis import HypothesisAgent, MarketSummary
|
||||
from multi_swarm.genome.hypothesis import HypothesisAgentGenome, ModelTier
|
||||
from multi_swarm.llm.client import CompletionResult
|
||||
from multi_swarm.llm.client import CompletionResult, EmptyCompletionError
|
||||
|
||||
|
||||
def make_summary() -> MarketSummary:
|
||||
@@ -214,3 +214,37 @@ def test_hypothesis_agent_no_retry_when_first_succeeds(mocker): # type: ignore[
|
||||
assert proposal.n_attempts == 1
|
||||
assert len(proposal.completions) == 1
|
||||
assert fake_llm.complete.call_count == 1
|
||||
|
||||
|
||||
def test_hypothesis_agent_retries_on_empty_completion(mocker): # type: ignore[no-untyped-def]
|
||||
"""LLMClient esaurisce retry tenacity → propose ritenta nel loop max_attempts."""
|
||||
fake_llm = mocker.MagicMock()
|
||||
fake_llm.complete.side_effect = [
|
||||
EmptyCompletionError("empty response from qwen"),
|
||||
CompletionResult(
|
||||
text=VALID_STRATEGY_JSON,
|
||||
input_tokens=200,
|
||||
output_tokens=80,
|
||||
tier=ModelTier.C,
|
||||
model="qwen",
|
||||
),
|
||||
]
|
||||
agent = HypothesisAgent(llm=fake_llm, max_retries=2)
|
||||
proposal = agent.propose(make_genome(), make_summary())
|
||||
assert proposal.strategy is not None
|
||||
assert fake_llm.complete.call_count == 2
|
||||
# n_attempts conta solo le completions arrivate (skipping empty failures).
|
||||
assert len(proposal.completions) == 1
|
||||
|
||||
|
||||
def test_hypothesis_agent_returns_failed_proposal_on_only_empty_completions(mocker): # type: ignore[no-untyped-def]
|
||||
"""Tutti i tentativi sollevano EmptyCompletionError → proposal con strategy None."""
|
||||
fake_llm = mocker.MagicMock()
|
||||
fake_llm.complete.side_effect = EmptyCompletionError("empty response")
|
||||
agent = HypothesisAgent(llm=fake_llm, max_retries=2)
|
||||
proposal = agent.propose(make_genome(), make_summary())
|
||||
assert proposal.strategy is None
|
||||
assert proposal.parse_error is not None
|
||||
assert "empty_completion" in proposal.parse_error
|
||||
# 3 tentativi tutti falliti.
|
||||
assert fake_llm.complete.call_count == 3
|
||||
|
||||
Reference in New Issue
Block a user