mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(gateway): normalize empty agent responses in inner runner early-return
When _run_agent_inner returns via its "not final_response" early path (e.g. an output-length truncation with no visible assistant text), it returned the raw "⚠️ Response truncated due to output length limit" string as final text. Messaging gateways then echoed that to users. Route this path through _normalize_empty_agent_response + _sanitize_gateway_final_response — the same treatment the outer _handle_message_with_agent path already applies — so users see the friendly "⚠️ Processing stopped: … Try again." instead, falling back to the raw error only when normalization yields nothing. Salvaged from #48034 by @djimit (gateway normalization half only; the retry-count/token-boost tuning in conversation_loop.py was dropped as symptom-tuning of an already-working recovery loop).
This commit is contained in:
parent
fb7a38ad21
commit
74ae07b612
3 changed files with 44 additions and 2 deletions
|
|
@ -17611,9 +17611,14 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
)
|
||||
|
||||
if not final_response:
|
||||
error_msg = f"⚠️ {result['error']}" if result.get("error") else ""
|
||||
final_response = _normalize_empty_agent_response(
|
||||
result, final_response or "", history_len=len(agent_history),
|
||||
)
|
||||
final_response = _sanitize_gateway_final_response(source.platform, final_response)
|
||||
if not final_response:
|
||||
final_response = f"⚠️ {result['error']}" if result.get("error") else ""
|
||||
return {
|
||||
"final_response": error_msg,
|
||||
"final_response": final_response,
|
||||
"messages": result.get("messages", []),
|
||||
"api_calls": result.get("api_calls", 0),
|
||||
"failed": result.get("failed", False),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
|
|||
# Auto-extracted from noreply emails + manual overrides
|
||||
AUTHOR_MAP = {
|
||||
"mac-studio@Fabios-Mac-Studio.local": "valenteff", # PR #53277 salvage (macOS launchd reload: retry bootstrap via _launchctl_bootstrap until launchctl-list confirms registration or the restart-drain window elapses; retry TimeoutExpired not just CalledProcessError; log persistent orphans)
|
||||
"info@djimit.nl": "djimit", # PR #48034 salvage (normalize empty agent responses in the proxy/inner runner's not-final_response early return so gateways surface "⚠️ Processing stopped: … Try again." instead of a raw truncation error)
|
||||
"gary@bitcryptic.com": "bitcryptic-gw", # PR #53997 salvage (Matrix E2EE: resolve device_id via query_keys({mxid: []}) when whoami returns none; guard verification call sites so query_keys is never sent [null]; reset _device_id_unverified at connect() start; disconnect before reconnect)
|
||||
"7698789+abchiaravalle@users.noreply.github.com": "abchiaravalle", # PR #46997 salvage (recover resume_pending sessions: dual freshness signal + empty-turn safety net so restart auto-resume never sends a blank user turn)
|
||||
"swissly@users.noreply.github.com": "swissly", # PR #47167 salvage (wrap cron delivery thread-pool fallback in its own try/except so a per-target failure can't escape the except-RuntimeError block and crash the multi-target delivery loop; #47163)
|
||||
|
|
|
|||
|
|
@ -106,6 +106,29 @@ class InterruptedAgent:
|
|||
return {"final_response": "interrupted", "messages": [], "api_calls": 1}
|
||||
|
||||
|
||||
class PartialTruncationAgent:
|
||||
"""Returns an incomplete turn with no visible assistant text."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.tool_progress_callback = kwargs.get("tool_progress_callback")
|
||||
self.tools = []
|
||||
self._interrupt_requested = False
|
||||
|
||||
@property
|
||||
def is_interrupted(self) -> bool:
|
||||
return self._interrupt_requested
|
||||
|
||||
def run_conversation(self, message, conversation_history=None, task_id=None):
|
||||
return {
|
||||
"final_response": None,
|
||||
"messages": [],
|
||||
"api_calls": 2,
|
||||
"completed": False,
|
||||
"partial": True,
|
||||
"error": "Response truncated due to output length limit",
|
||||
}
|
||||
|
||||
|
||||
def _make_runner(adapter):
|
||||
gateway_run = importlib.import_module("gateway.run")
|
||||
GatewayRunner = gateway_run.GatewayRunner
|
||||
|
|
@ -181,6 +204,19 @@ async def test_baseline_non_interrupted_agent_renders_progress(monkeypatch, tmp_
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_partial_empty_agent_response_is_normalized(monkeypatch, tmp_path):
|
||||
"""Messaging gateways should not echo raw truncation errors as final text."""
|
||||
adapter, result = await _run_once(
|
||||
monkeypatch, tmp_path, PartialTruncationAgent, "sess-partial-empty"
|
||||
)
|
||||
|
||||
assert result["final_response"].startswith("⚠️ Processing stopped:")
|
||||
assert "Response truncated due to output length limit" in result["final_response"]
|
||||
assert result["final_response"] != "⚠️ Response truncated due to output length limit"
|
||||
assert result["partial"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_progress_suppressed_when_agent_is_interrupted(monkeypatch, tmp_path):
|
||||
"""Post-interrupt tool.started events must not render as bubbles.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue