mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-27 01:11:40 +00:00
fix(errors): classify OpenRouter privacy-guardrail 404s distinctly (#14943)
OpenRouter returns a 404 with the specific message 'No endpoints available matching your guardrail restrictions and data policy. Configure: https://openrouter.ai/settings/privacy' when a user's account-level privacy setting excludes the only endpoint serving a model (e.g. DeepSeek V4 Pro, which today is hosted only by DeepSeek's own endpoint that may log inputs). Before this change we classified it as model_not_found, which was misleading (the model exists) and triggered provider fallback (useless — the same account setting applies to every OpenRouter call). Now it classifies as a new FailoverReason.provider_policy_blocked with retryable=False, should_fallback=False. The error body already contains the fix URL, so the user still gets actionable guidance.
This commit is contained in:
parent
acdcb167fb
commit
2acc8783d1
2 changed files with 104 additions and 0 deletions
|
|
@ -56,6 +56,7 @@ class TestFailoverReason:
|
|||
"overloaded", "server_error", "timeout",
|
||||
"context_overflow", "payload_too_large",
|
||||
"model_not_found", "format_error",
|
||||
"provider_policy_blocked",
|
||||
"thinking_signature", "long_context_tier", "unknown",
|
||||
}
|
||||
actual = {r.value for r in FailoverReason}
|
||||
|
|
@ -308,6 +309,59 @@ class TestClassifyApiError:
|
|||
assert result.retryable is True
|
||||
assert result.should_fallback is False
|
||||
|
||||
# ── Provider policy-block (OpenRouter privacy/guardrail) ──
|
||||
|
||||
def test_404_openrouter_policy_blocked(self):
|
||||
# Real OpenRouter error when the user's account privacy setting
|
||||
# excludes the only endpoint serving a model (e.g. DeepSeek V4 Pro
|
||||
# which is hosted only by DeepSeek, and their endpoint may log
|
||||
# inputs). Must NOT classify as model_not_found — the model
|
||||
# exists, falling back won't help (same account setting applies),
|
||||
# and the error body already tells the user where to fix it.
|
||||
e = MockAPIError(
|
||||
"No endpoints available matching your guardrail restrictions "
|
||||
"and data policy. Configure: https://openrouter.ai/settings/privacy",
|
||||
status_code=404,
|
||||
)
|
||||
result = classify_api_error(e)
|
||||
assert result.reason == FailoverReason.provider_policy_blocked
|
||||
assert result.retryable is False
|
||||
assert result.should_fallback is False
|
||||
|
||||
def test_400_openrouter_policy_blocked(self):
|
||||
# Defense-in-depth: if OpenRouter ever returns this as 400 instead
|
||||
# of 404, still classify it distinctly rather than as format_error
|
||||
# or model_not_found.
|
||||
e = MockAPIError(
|
||||
"No endpoints available matching your data policy",
|
||||
status_code=400,
|
||||
)
|
||||
result = classify_api_error(e)
|
||||
assert result.reason == FailoverReason.provider_policy_blocked
|
||||
assert result.retryable is False
|
||||
assert result.should_fallback is False
|
||||
|
||||
def test_message_only_openrouter_policy_blocked(self):
|
||||
# No status code — classifier should still catch the fingerprint
|
||||
# via the message-pattern fallback.
|
||||
e = Exception(
|
||||
"No endpoints available matching your guardrail restrictions "
|
||||
"and data policy"
|
||||
)
|
||||
result = classify_api_error(e)
|
||||
assert result.reason == FailoverReason.provider_policy_blocked
|
||||
|
||||
def test_404_model_not_found_still_works(self):
|
||||
# Regression guard: the new policy-block check must not swallow
|
||||
# genuine model_not_found 404s.
|
||||
e = MockAPIError(
|
||||
"openrouter/nonexistent-model is not a valid model ID",
|
||||
status_code=404,
|
||||
)
|
||||
result = classify_api_error(e)
|
||||
assert result.reason == FailoverReason.model_not_found
|
||||
assert result.should_fallback is True
|
||||
|
||||
# ── Payload too large ──
|
||||
|
||||
def test_413_payload_too_large(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue