mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(agent): preserve nested API error bodies
This commit is contained in:
parent
5ab4136631
commit
e7c013494d
2 changed files with 46 additions and 13 deletions
|
|
@ -1317,19 +1317,25 @@ def _extract_status_code(error: Exception) -> Optional[int]:
|
|||
|
||||
|
||||
def _extract_error_body(error: Exception) -> dict:
|
||||
"""Extract the structured error body from an SDK exception."""
|
||||
body = getattr(error, "body", None)
|
||||
if isinstance(body, dict):
|
||||
return body
|
||||
# Some errors have .response.json()
|
||||
response = getattr(error, "response", None)
|
||||
if response is not None:
|
||||
try:
|
||||
json_body = response.json()
|
||||
if isinstance(json_body, dict):
|
||||
return json_body
|
||||
except Exception:
|
||||
pass
|
||||
"""Extract the structured error body from an SDK exception or its cause chain."""
|
||||
current = error
|
||||
for _ in range(5): # Match _extract_status_code() traversal depth.
|
||||
body = getattr(current, "body", None)
|
||||
if isinstance(body, dict):
|
||||
return body
|
||||
# Some errors have .response.json()
|
||||
response = getattr(current, "response", None)
|
||||
if response is not None:
|
||||
try:
|
||||
json_body = response.json()
|
||||
if isinstance(json_body, dict):
|
||||
return json_body
|
||||
except Exception:
|
||||
pass
|
||||
cause = getattr(current, "__cause__", None) or getattr(current, "__context__", None)
|
||||
if cause is None or cause is current:
|
||||
break
|
||||
current = cause
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,18 @@ class TestExtractErrorBody:
|
|||
e = MockAPIError("fail", body={"error": {"message": "bad"}})
|
||||
assert _extract_error_body(e) == {"error": {"message": "bad"}}
|
||||
|
||||
def test_from_cause_chain_body_attr(self):
|
||||
inner = MockAPIError(
|
||||
"inner",
|
||||
status_code=402,
|
||||
body={"error": {"message": "Usage limit reached, try again in 5 minutes"}},
|
||||
)
|
||||
outer = Exception("outer")
|
||||
outer.__cause__ = inner
|
||||
assert _extract_error_body(outer) == {
|
||||
"error": {"message": "Usage limit reached, try again in 5 minutes"},
|
||||
}
|
||||
|
||||
def test_empty_when_no_body(self):
|
||||
assert _extract_error_body(Exception("generic")) == {}
|
||||
|
||||
|
|
@ -300,6 +312,21 @@ class TestClassifyApiError:
|
|||
assert result.retryable is False
|
||||
assert result.should_fallback is True
|
||||
|
||||
def test_wrapped_402_uses_nested_body_message(self):
|
||||
inner = MockAPIError(
|
||||
"inner",
|
||||
status_code=402,
|
||||
body={"error": {"message": "Usage limit reached, try again in 5 minutes"}},
|
||||
)
|
||||
outer = Exception("outer")
|
||||
outer.__cause__ = inner
|
||||
|
||||
result = classify_api_error(outer)
|
||||
|
||||
assert result.reason == FailoverReason.rate_limit
|
||||
assert result.retryable is True
|
||||
assert result.message == "Usage limit reached, try again in 5 minutes"
|
||||
|
||||
# ── Rate limit ──
|
||||
|
||||
def test_429_rate_limit(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue