Skip to content
Open
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
81 changes: 81 additions & 0 deletions src/openhuman/inference/provider/config_rejection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
//! from a user OAuth/scope gap)
//! - `"not_found_error"` (J2 / J5 / J4 — litellm-compatible envelope
//! `type` field carrying "model 'X' not found")
//! - `"does not support tools"` / `"function calling is not supported"` /
//! `"unknown parameter: tools"` / `"unrecognized field \`tools\`"` /
//! `"unsupported parameter: tools"` (TAURI-RUST-4K7 — Ollama models such
//! as `gemma3:1b-it-qat` and `huihui_ai/deepseek-r1-abliterated:8b`
//! reject tool-enabled requests with HTTP 400. The compatible provider
//! already retries without tools, so the initial 400 is not a
//! bug — it's expected discovery of the model's capability boundary.
//! Sentry noise suppressed here; the retry path in `compatible.rs` runs
//! unchanged.)
//!
//! These are **deterministic user-configuration state**, not bugs the
//! maintainers can act on: the user pointed OpenHuman at a custom
Expand Down Expand Up @@ -148,6 +157,18 @@ pub fn is_provider_config_rejection_message(body: &str) -> bool {
// this is the `type` field used by litellm/Anthropic-style
// envelopes for the same class of user-state error.
"not_found_error",
// TAURI-RUST-4K7 — Ollama models that don't support tool calling
// (e.g. gemma3:1b-it-qat, huihui_ai/deepseek-r1-abliterated:8b)
// return HTTP 400 with one of these phrases. The compatible
// provider (`compatible.rs`) detects the error and retries
// without tools, so the 400 is expected capability-discovery
// rather than a product bug. Suppress Sentry to avoid noise from
// the first-attempt rejection that precedes the successful retry.
"does not support tools",
"function calling is not supported",
"unknown parameter: tools",
"unrecognized field `tools`",
"unsupported parameter: tools",
];

let lower = body.to_ascii_lowercase();
Expand Down Expand Up @@ -337,4 +358,64 @@ mod tests {
);
}
}

/// TAURI-RUST-4K7 — Ollama models that don't support tool calling
/// (e.g. `gemma3:1b-it-qat`, `huihui_ai/deepseek-r1-abliterated:8b`)
/// return HTTP 400 with one of several tool-rejection phrases.
/// The compatible provider retries without tools, so the 400 is expected
/// capability-discovery rather than a product bug. These phrases must be
/// classified as config-rejections so Sentry is not flooded on every turn.
#[test]
fn detects_ollama_tool_unsupported_bodies() {
for (sentry_id, body) in [
(
"4K7-a",
r#"{"error":"gemma3:1b-it-qat does not support tools"}"#,
),
(
"4K7-b",
r#"{"error":"huihui_ai/deepseek-r1-abliterated:8b does not support tools"}"#,
),
(
"4K7-c",
r#"ollama streaming API error (400 Bad Request): {"error":"phi3:mini does not support tools"}"#,
),
(
"4K7-d",
r#"{"error":"function calling is not supported by this model"}"#,
),
(
"4K7-e",
r#"{"error":{"message":"unknown parameter: tools","type":"invalid_request_error"}}"#,
),
(
"4K7-f",
r#"{"error":"unrecognized field `tools` in request body"}"#,
),
(
"4K7-g",
r#"{"error":{"message":"unsupported parameter: tools","type":"invalid_request_error"}}"#,
),
] {
assert!(
is_provider_config_rejection_message(body),
"TAURI-RUST-{sentry_id} body must classify as provider config-rejection (tool-unsupported): {body:?}"
);
}
}

#[test]
fn detects_ollama_tool_unsupported_bodies_case_insensitive() {
// Ollama error messages should match regardless of casing.
for body in [
"Model 'gemma3:1b-it-qat' DOES NOT SUPPORT TOOLS",
"Function Calling Is Not Supported By This Model",
"Unknown Parameter: Tools",
] {
assert!(
is_provider_config_rejection_message(body),
"{body:?} must classify as config-rejection regardless of case"
);
}
}
}
Loading