fix(observability): demote OpenHuman backend unknown-model 400 (TAURI-RUST-2Z1) (#2464)

Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
CodeGhost21
2026-05-23 01:31:05 -07:00
committed by GitHub
co-authored by Steven Enamakel
parent a2d1b122ce
commit fe37b132ee
3 changed files with 164 additions and 18 deletions
@@ -44,20 +44,32 @@
//!
//! ## Provider-aware polarity (important)
//!
//! The phrases below are emitted by **third-party upstream APIs**
//! Most of the phrases below are emitted by **third-party upstream APIs**
//! (DeepSeek / OpenRouter / Moonshot). The OpenHuman hosted backend
//! resolves tier aliases natively and never emits "supported API model
//! names are deepseek-…" or "invalid temperature: only 1 is allowed" — so
//! the phrase set is intrinsically scoped to custom providers. The
//! that phrase set is intrinsically scoped to custom providers. The
//! HTTP-layer wrapper [`super::ops::is_provider_config_rejection_http`]
//! additionally guards on `provider != openhuman_backend::PROVIDER_LABEL`
//! so a model-rejection from our **own** backend (which would be a real
//! polarity-guards those phrases on `provider !=
//! openhuman_backend::PROVIDER_LABEL` so a model-rejection from our
//! **own** backend that we did not expect (which would be a real
//! regression we sent it a bad request) still reaches Sentry. The
//! message-only predicate is consumed by
//! [`crate::core::observability::expected_error_kind`] for the
//! re-reported error that escapes the provider layer and is raised again
//! by `agent.run_single` / `web_channel.run_chat_task`.
//!
//! **Exception: the OpenAI-compatible "unknown model" shape** (`Model 'X'
//! is not available. Use GET /openai/v1/models …`) is now emitted by the
//! OpenHuman hosted backend too, in response to user-configured model ids
//! that aren't in the backend's registry. Pinned by
//! [`is_openai_compatible_unknown_model_message`]. The HTTP-layer wrapper
//! drops the polarity guard for that specific shape so the same body is
//! treated as user-state regardless of provider — see TAURI-RUST-2Z1
//! where a user-typed `MiniMax-M2.7-highspeed` model id (plus two
//! `custom:` fallback variants from their own `model_fallbacks` config)
//! was rejected with this wire shape and otherwise reached Sentry.
//!
//! Keep the list deliberately tight: a false positive demotes a real
//! provider/backend bug to an info log.
@@ -142,6 +154,29 @@ pub fn is_provider_config_rejection_message(body: &str) -> bool {
PHRASES.iter().any(|phrase| lower.contains(phrase))
}
/// Returns true if a provider error body matches the OpenAI-compatible
/// "unknown model" shape — anchored on the `/openai/v1/models`
/// remediation hint the upstream returns alongside `Model 'X' is not
/// available.`.
///
/// This is a strict subset of [`is_provider_config_rejection_message`]:
/// the same phrase already lives in that predicate's list. The narrower
/// helper exists so the HTTP-layer wrapper
/// ([`super::ops::is_provider_config_rejection_http`]) can drop its
/// `provider != openhuman_backend::PROVIDER_LABEL` polarity guard for
/// this specific body shape — the OpenHuman hosted backend now emits the
/// same OpenAI-compatible "Model 'X' is not available" wire body in
/// response to user-configured unknown model ids, so the original
/// polarity assumption ("only third-party providers speak this dialect")
/// no longer holds.
///
/// Drops TAURI-RUST-2Z1 (per-attempt) — the aggregate sibling
/// TAURI-RUST-2Z2 is already covered by the message-only classifier in
/// [`crate::core::observability::expected_error_kind`].
pub fn is_openai_compatible_unknown_model_message(body: &str) -> bool {
body.to_ascii_lowercase().contains("/openai/v1/models")
}
#[cfg(test)]
mod tests {
use super::*;
@@ -252,4 +287,54 @@ mod tests {
);
}
}
#[test]
fn unknown_model_helper_matches_openai_compatible_bodies() {
// TAURI-RUST-2Z1 — the OpenHuman hosted backend now emits the
// OpenAI-compatible "Model 'X' is not available" wire body for
// user-configured unknown model ids. The helper is anchored on
// the `/openai/v1/models` remediation hint so the same body shape
// matches whether it came from a third-party `custom_openai`
// upstream or our own backend.
for body in [
r#"OpenHuman API error (400 Bad Request): {"success":false,"error":"Model 'MiniMax-M2.7-highspeed' is not available. Use GET /openai/v1/models to list available models."}"#,
r#"OpenHuman API error (400 Bad Request): {"success":false,"error":"Model 'custom:MiniMax-M2.7' is not available. Use GET /openai/v1/models to list available models."}"#,
"Model 'deepseek-v4-pro' is not available. Use GET /openai/v1/models to list available models.",
] {
assert!(
is_openai_compatible_unknown_model_message(body),
"TAURI-RUST-2Z1 body must classify as openai-compatible unknown model: {body:?}"
);
// Sanity: must remain a member of the broader phrase set so
// the message-only classifier in
// `crate::core::observability::expected_error_kind` keeps
// demoting the aggregate (TAURI-RUST-2Z2).
assert!(
is_provider_config_rejection_message(body),
"broader classifier must continue to match: {body:?}"
);
}
}
#[test]
fn unknown_model_helper_rejects_other_config_rejection_phrases() {
// Polarity exception must stay narrow: other config-rejection
// shapes (DeepSeek `supported api model names are`, Moonshot
// `invalid temperature`, OpenRouter `requires more credits`, …)
// must still go through the provider-polarity guard so a
// hypothetical regression where our own backend emits one of
// those phrases reaches Sentry.
for body in [
"The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed reasoning-v1.",
"invalid temperature: only 1 is allowed for this model",
"The model `gpt-5.5` does not exist or you do not have access to it.",
r#"{"error":{"message":"model not found","code":"model_not_found"}}"#,
"This request requires more credits, or fewer max_tokens.",
] {
assert!(
!is_openai_compatible_unknown_model_message(body),
"{body:?} must NOT match the narrow openai-compatible-unknown-model helper"
);
}
}
}
+3 -1
View File
@@ -28,6 +28,8 @@ pub use traits::{
};
pub use billing_error::is_budget_exhausted_message;
pub use config_rejection::is_provider_config_rejection_message;
pub use config_rejection::{
is_openai_compatible_unknown_model_message, is_provider_config_rejection_message,
};
pub use factory::{create_chat_provider, provider_for_role, BYOK_INCOMPLETE_SENTINEL};
pub use ops::*;
+72 -13
View File
@@ -478,21 +478,44 @@ pub(super) fn log_provider_access_policy_denied_http_403(
/// abstract tier leaked to a custom provider, model-specific temperature
/// constraint) that should be demoted from Sentry to an info log.
///
/// Provider-aware (inverted polarity vs. the 401/403 backend rule): the
/// same body from the OpenHuman **backend** stays Sentry-actionable —
/// that would mean we sent our own backend a bad request (a regression,
/// e.g. #2079). Only client errors from a *custom / third-party*
/// provider are user-config state. Restricted to the observed shapes
/// (400 invalid-param / unknown-model, 404 model-does-not-exist, 422
/// unprocessable); 408/429 are transient and handled separately.
/// Provider-aware (inverted polarity vs. the 401/403 backend rule): for
/// most config-rejection phrases the same body from the OpenHuman
/// **backend** stays Sentry-actionable — that would mean we sent our own
/// backend a bad request (a regression, e.g. #2079). Restricted to the
/// observed shapes (400 invalid-param / unknown-model, 404
/// model-does-not-exist, 422 unprocessable); 408/429 are transient and
/// handled separately.
///
/// **Exception: OpenAI-compatible "unknown model"** (`Model 'X' is not
/// available. Use GET /openai/v1/models …`). The OpenHuman backend now
/// emits this exact body for user-configured unknown model ids, so it is
/// user-state regardless of provider — the polarity guard is dropped for
/// this specific shape (TAURI-RUST-2Z1). See
/// [`super::is_openai_compatible_unknown_model_message`].
pub(super) fn is_provider_config_rejection_http(
status: reqwest::StatusCode,
provider: &str,
body: &str,
) -> bool {
matches!(status.as_u16(), 400 | 404 | 422)
&& provider != openhuman_backend::PROVIDER_LABEL
&& super::is_provider_config_rejection_message(body)
if !matches!(status.as_u16(), 400 | 404 | 422) {
return false;
}
if !super::is_provider_config_rejection_message(body) {
return false;
}
// OpenAI-compatible "unknown model" body is user-state regardless of
// provider — both third-party `custom_openai` upstreams and our own
// OpenHuman backend now emit it for user-configured model ids that
// aren't in the registry (TAURI-RUST-2Z1).
if super::is_openai_compatible_unknown_model_message(body) {
return true;
}
// Remaining config-rejection phrases (DeepSeek `supported api model
// names are`, Moonshot `invalid temperature`, litellm envelopes, …)
// are intrinsically scoped to third-party providers — keep the
// polarity guard so a regression where our own backend emits one of
// those still reaches Sentry.
provider != openhuman_backend::PROVIDER_LABEL
}
pub(super) fn log_provider_config_rejection(
@@ -1375,14 +1398,50 @@ mod tests {
#[test]
fn openhuman_backend_same_body_is_not_suppressed() {
// Inverted polarity: a model-rejection from our OWN backend
// means we sent it a bad request — a real regression that must
// still reach Sentry. (Mirror of the 401/403 backend rule.)
// Inverted polarity: for tier-leak / temperature / litellm /
// OpenRouter-style phrases, the OpenHuman backend never
// emits them, so the same body from our OWN backend would
// mean we sent it a bad request — a real regression that
// must still reach Sentry. (Mirror of the 401/403 backend
// rule.)
assert!(!is_provider_config_rejection_http(
reqwest::StatusCode::BAD_REQUEST,
openhuman_backend::PROVIDER_LABEL,
TIER_LEAK_BODY,
));
assert!(!is_provider_config_rejection_http(
reqwest::StatusCode::BAD_REQUEST,
openhuman_backend::PROVIDER_LABEL,
TEMP_BODY,
));
}
#[test]
fn openhuman_backend_openai_compatible_unknown_model_is_suppressed() {
// TAURI-RUST-2Z1 — the OpenHuman backend DOES emit the
// OpenAI-compatible "Model 'X' is not available. Use GET
// /openai/v1/models …" wire body for user-configured unknown
// model ids (here `MiniMax-M2.7-highspeed` and two
// `custom:`-prefixed fallback variants from the user's own
// `model_fallbacks` config). That's user-state, not a
// regression — drop the polarity guard for this specific
// shape so the per-attempt event stops reaching Sentry.
// (The aggregate sibling TAURI-RUST-2Z2 is already covered by
// `expected_error_kind` via the broader message-only
// classifier.)
for body in [
r#"OpenHuman API error (400 Bad Request): {"success":false,"error":"Model 'MiniMax-M2.7-highspeed' is not available. Use GET /openai/v1/models to list available models."}"#,
r#"OpenHuman API error (400 Bad Request): {"success":false,"error":"Model 'custom:MiniMax-M2.7' is not available. Use GET /openai/v1/models to list available models."}"#,
] {
assert!(
is_provider_config_rejection_http(
reqwest::StatusCode::BAD_REQUEST,
openhuman_backend::PROVIDER_LABEL,
body,
),
"TAURI-RUST-2Z1 body must be suppressed for openhuman backend: {body:?}"
);
}
}
#[test]