Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions src/openhuman/channels/providers/web_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
senamakel-droid marked this conversation as resolved.
Resolved
// 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 {
Expand Down Expand Up @@ -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,
Expand 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,
Expand Down
40 changes: 40 additions & 0 deletions src/openhuman/channels/providers/web_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
));
Expand All @@ -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]
Expand Down
7 changes: 5 additions & 2 deletions tests/channels_provider_leftovers_raw_coverage_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading