mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
Merge 4d27bd4a3b into 05d8f11085
This commit is contained in:
commit
ec99f88a3d
4 changed files with 133 additions and 27 deletions
47
tests/gateway/test_empty_response_handling.py
Normal file
47
tests/gateway/test_empty_response_handling.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""Tests for empty-response fallback handling in GatewayRunner."""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mock_dotenv(monkeypatch):
|
||||
"""gateway.run imports dotenv at module level; stub it for tests."""
|
||||
fake = types.ModuleType("dotenv")
|
||||
fake.load_dotenv = lambda *a, **kw: None
|
||||
monkeypatch.setitem(sys.modules, "dotenv", fake)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def runner():
|
||||
from gateway.run import GatewayRunner
|
||||
|
||||
return GatewayRunner.__new__(GatewayRunner)
|
||||
|
||||
|
||||
class TestEmptyResponseFallback:
|
||||
def test_reasoning_only_message(self, runner):
|
||||
message = runner._build_empty_response_message({
|
||||
"empty_response_reasoning": "structured reasoning answer",
|
||||
})
|
||||
assert message == (
|
||||
"⚠️ The model produced internal reasoning but no visible response after all retries. "
|
||||
"Try again or rephrase your question."
|
||||
)
|
||||
|
||||
def test_truly_empty_message(self, runner):
|
||||
message = runner._build_empty_response_message({})
|
||||
assert message == (
|
||||
"⚠️ The model returned no content after all retries. "
|
||||
"Try again or rephrase your question."
|
||||
)
|
||||
|
||||
def test_response_fallback_detection_handles_new_and_legacy_forms(self, runner):
|
||||
assert runner._is_empty_response_fallback(
|
||||
{"response_is_empty_fallback": True},
|
||||
"⚠️ The model returned no content after all retries. Try again or rephrase your question.",
|
||||
)
|
||||
assert runner._is_empty_response_fallback({}, "(empty)")
|
||||
assert not runner._is_empty_response_fallback({}, "hello world")
|
||||
|
|
@ -250,6 +250,15 @@ def _mock_response(
|
|||
resp.usage = None
|
||||
return resp
|
||||
|
||||
EMPTY_REASONING_RESPONSE = (
|
||||
"⚠️ The model produced internal reasoning but no visible response after all retries. "
|
||||
"Try again or rephrase your question."
|
||||
)
|
||||
EMPTY_TRULY_EMPTY_RESPONSE = (
|
||||
"⚠️ The model returned no content after all retries. "
|
||||
"Try again or rephrase your question."
|
||||
)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Group 1: Pure Functions
|
||||
|
|
@ -2256,7 +2265,9 @@ class TestRunConversation:
|
|||
|
||||
mock_compress.assert_not_called() # no compression triggered
|
||||
assert result["completed"] is True
|
||||
assert result["final_response"] == "(empty)"
|
||||
assert result["final_response"] == EMPTY_TRULY_EMPTY_RESPONSE
|
||||
assert result["response_is_empty_fallback"] is True
|
||||
assert result["empty_response_reasoning"] is None
|
||||
assert result["api_calls"] == 6 # 1 original + 2 prefill + 3 retries
|
||||
|
||||
def test_reasoning_only_response_prefill_then_empty(self, agent):
|
||||
|
|
@ -2276,7 +2287,9 @@ class TestRunConversation:
|
|||
):
|
||||
result = agent.run_conversation("answer me")
|
||||
assert result["completed"] is True
|
||||
assert result["final_response"] == "(empty)"
|
||||
assert result["final_response"] == EMPTY_REASONING_RESPONSE
|
||||
assert result["response_is_empty_fallback"] is True
|
||||
assert result["empty_response_reasoning"] == "structured reasoning answer"
|
||||
assert result["api_calls"] == 6 # 1 original + 2 prefill + 3 retries
|
||||
|
||||
def test_reasoning_only_prefill_succeeds_on_continuation(self, agent):
|
||||
|
|
@ -2323,7 +2336,9 @@ class TestRunConversation:
|
|||
):
|
||||
result = agent.run_conversation("answer me")
|
||||
assert result["completed"] is True
|
||||
assert result["final_response"] == "(empty)"
|
||||
assert result["final_response"] == EMPTY_TRULY_EMPTY_RESPONSE
|
||||
assert result["response_is_empty_fallback"] is True
|
||||
assert result["empty_response_reasoning"] is None
|
||||
assert result["api_calls"] == 4 # 1 original + 3 retries
|
||||
|
||||
def test_truly_empty_response_succeeds_on_nudge(self, agent):
|
||||
|
|
@ -2419,7 +2434,8 @@ class TestRunConversation:
|
|||
):
|
||||
result = agent.run_conversation("answer me")
|
||||
assert result["completed"] is True
|
||||
assert result["final_response"] == "(empty)"
|
||||
assert result["final_response"] == EMPTY_TRULY_EMPTY_RESPONSE
|
||||
assert result["response_is_empty_fallback"] is True
|
||||
|
||||
def test_empty_response_emits_status_for_gateway(self, agent):
|
||||
"""_emit_status is called during empty retries so gateway users see feedback."""
|
||||
|
|
@ -2445,7 +2461,8 @@ class TestRunConversation:
|
|||
):
|
||||
result = agent.run_conversation("answer me")
|
||||
|
||||
assert result["final_response"] == "(empty)"
|
||||
assert result["final_response"] == EMPTY_TRULY_EMPTY_RESPONSE
|
||||
assert result["response_is_empty_fallback"] is True
|
||||
# Should have emitted retry statuses (3 retries) + final failure
|
||||
retry_msgs = [m for m in status_messages if "retrying" in m.lower()]
|
||||
assert len(retry_msgs) == 3, f"Expected 3 retry status messages, got {len(retry_msgs)}: {status_messages}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue