mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(inference): surface actionable error when Managed route fails with no credits (#3121)
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
1f463dd7ea
commit
e9c38ea615
@@ -17,13 +17,33 @@ pub(crate) fn is_inference_budget_exceeded_error(message: &str) -> bool {
|
||||
let normalized = BUDGET_ERROR_NORMALIZE_RE
|
||||
.replace_all(&message.trim().to_ascii_lowercase(), " ")
|
||||
.into_owned();
|
||||
BUDGET_ERROR_PATTERNS
|
||||
if BUDGET_ERROR_PATTERNS
|
||||
.iter()
|
||||
.any(|pattern| pattern.is_match(&normalized))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Align with the canonical OpenHuman-backend budget detector
|
||||
// (`billing_error::is_budget_exhausted_message`) so the managed
|
||||
// no-credits response — a 400 carrying "Insufficient budget" /
|
||||
// "Insufficient balance" — surfaces the actionable budget message
|
||||
// below instead of the generic "Something went wrong" apology
|
||||
// (issue #3088). Without this, an Ollama user with zero credits and
|
||||
// routing still on Managed sees an opaque "provider error" and has no
|
||||
// way to self-diagnose that they must top up or switch routing.
|
||||
crate::openhuman::inference::provider::is_budget_exhausted_message(message)
|
||||
}
|
||||
|
||||
pub(crate) fn inference_budget_exceeded_user_message() -> &'static str {
|
||||
"I don't have any budget available right now. Please top up your credits or choose a plan to continue."
|
||||
// Keep the literal "top up" / "credits" tokens (asserted by
|
||||
// `budget_exceeded_copy_mentions_top_up`) and add the self-diagnosis
|
||||
// path for issue #3088: a user who enabled a local model but left
|
||||
// routing on Managed needs to know they can switch to their own model
|
||||
// rather than being stuck. We guide, never auto-switch — the user's
|
||||
// routing choice in Settings is respected.
|
||||
"You're out of credits, so I can't run the managed (cloud) model right now. \
|
||||
You can top up your credits or pick a plan to continue — or, if you've enabled a \
|
||||
local model like Ollama, switch routing to \"Use Your Own Models\" in Settings → AI Configuration."
|
||||
}
|
||||
|
||||
pub(crate) fn generic_inference_error_user_message() -> &'static str {
|
||||
@@ -379,6 +399,12 @@ pub(crate) fn classify_inference_error(err: &str) -> ClassifiedError {
|
||||
} else if lower.contains("402")
|
||||
|| lower.contains("payment required")
|
||||
|| lower.contains("insufficient balance")
|
||||
// Issue #3088: the OpenHuman managed backend reports no-credits as a
|
||||
// 400 with "Insufficient budget" (not a 402), which previously fell
|
||||
// through to the generic "Something went wrong" branch. Catch the
|
||||
// canonical budget phrases here so the user gets the actionable
|
||||
// top-up / switch-to-your-own-model guidance instead.
|
||||
|| is_inference_budget_exceeded_error(err)
|
||||
{
|
||||
// `openhuman_billing` means OpenHuman's own credit/quota system —
|
||||
// a 402 carrying the "openhuman" envelope (or no envelope at all,
|
||||
@@ -392,7 +418,7 @@ pub(crate) fn classify_inference_error(err: &str) -> ClassifiedError {
|
||||
};
|
||||
ClassifiedError {
|
||||
error_type: "budget_exhausted",
|
||||
message: with_provider_detail("Insufficient credits. Please top up to continue.", err),
|
||||
message: with_provider_detail(inference_budget_exceeded_user_message(), err),
|
||||
source,
|
||||
retryable: false,
|
||||
retry_after_ms: None,
|
||||
|
||||
@@ -134,6 +134,16 @@ fn detects_backend_budget_exhaustion_error() {
|
||||
assert!(is_inference_budget_exceeded_error(
|
||||
"provider error: budget exceeded, please add credits"
|
||||
));
|
||||
// Issue #3088: the OpenHuman managed backend reports no-credits as a
|
||||
// 400 carrying these canonical phrases (see `billing_error.rs`). They
|
||||
// were previously NOT recognised here, so the error fell through to the
|
||||
// generic "Something went wrong" branch. They must now match.
|
||||
assert!(is_inference_budget_exceeded_error(
|
||||
"openhuman API error (400 Bad Request): Insufficient budget"
|
||||
));
|
||||
assert!(is_inference_budget_exceeded_error(
|
||||
"openhuman API error (400 Bad Request): Insufficient balance"
|
||||
));
|
||||
assert!(!is_inference_budget_exceeded_error(
|
||||
"OpenHuman API error (500): Internal server error"
|
||||
));
|
||||
@@ -144,6 +154,36 @@ fn budget_exceeded_copy_mentions_top_up() {
|
||||
let message = inference_budget_exceeded_user_message();
|
||||
assert!(message.contains("top up"));
|
||||
assert!(message.contains("credits"));
|
||||
// Issue #3088: the copy must guide the user to the self-service fix —
|
||||
// switching routing to their own local model — so an Ollama user with
|
||||
// no credits can self-diagnose. We guide, never auto-switch.
|
||||
assert!(message.contains("Use Your Own Models"));
|
||||
assert!(message.contains("Settings"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_inference_error_managed_insufficient_budget_400_is_budget_exhausted() {
|
||||
// Issue #3088: a managed (OpenHuman backend) no-credits failure arrives
|
||||
// as a 400 with "Insufficient budget" — NOT a 402. It previously fell
|
||||
// through to the generic `inference` branch ("Something went wrong"),
|
||||
// leaving the user unable to self-diagnose. It must now classify as
|
||||
// budget_exhausted with actionable, non-retryable copy.
|
||||
let raw = "openhuman API error (400 Bad Request): Insufficient budget";
|
||||
let classified = classify_inference_error(raw);
|
||||
assert_eq!(classified.error_type, "budget_exhausted");
|
||||
assert_eq!(
|
||||
classified.source, "openhuman_billing",
|
||||
"the OpenHuman backend's own credit system is the origin"
|
||||
);
|
||||
assert!(
|
||||
!classified.retryable,
|
||||
"out of credits — retrying the same prompt won't help"
|
||||
);
|
||||
assert!(
|
||||
classified.message.contains("Use Your Own Models"),
|
||||
"must guide the user to switch routing: {}",
|
||||
classified.message
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -318,11 +318,14 @@ async fn web_round19_covers_classifier_variants_and_cancel_cleanup() {
|
||||
assert_eq!(auth.source, "config");
|
||||
assert!(!auth.retryable);
|
||||
|
||||
// Issue #3088: budget-signal strings now classify as `budget_exhausted`
|
||||
// instead of falling through to the generic `inference` branch — the
|
||||
// user gets an actionable "top up or switch routing" message.
|
||||
let budget = web_test_support::classify_error_for_test(
|
||||
"inference budget exceeded: monthly limit reached",
|
||||
);
|
||||
assert_eq!(budget.error_type, "inference");
|
||||
assert_eq!(budget.source, "provider");
|
||||
assert_eq!(budget.error_type, "budget_exhausted");
|
||||
assert_eq!(budget.source, "openhuman_billing");
|
||||
|
||||
let network = web_test_support::classify_error_for_test(
|
||||
"request error: dns error while trying to connect",
|
||||
|
||||
Reference in New Issue
Block a user