From c146a103aa917296b55bb3a3eabc1233a23e41ea Mon Sep 17 00:00:00 2001 From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Date: Thu, 28 May 2026 23:07:20 +0530 Subject: [PATCH] fix(inference): suppress Sentry noise when ollama model doesn't support tools (closes #2787) (#2813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds `"does not support tools"`, `"function calling is not supported"`, `"unknown parameter: tools"`, `"unrecognized field \`tools\`"`, and `"unsupported parameter: tools"` to `is_provider_config_rejection_message` in `config_rejection.rs` - These are the phrases Ollama returns (HTTP 400) when a model like `gemma3:1b-it-qat` or `huihui_ai/deepseek-r1-abliterated:8b` receives a request with tool definitions - The compatible provider already retries without tools (streaming path), so the initial 400 is expected capability discovery — not a product bug - Before this fix the 400 fell through all no-Sentry conditions and was reported on every agent turn (TAURI-RUST-4K7, 14+ events) ## Root cause `is_provider_config_rejection_message` did not include the Ollama tool-unsupported phrase family. Consequently `is_provider_config_rejection_http` returned `false`, and `stream_native_chat`'s error handler fell through to `should_report_provider_http_failure(400) == true`, firing a Sentry event before returning the error that triggers the no-tools retry. ## What the fix does Adds the five tool-unsupported phrases to the `PHRASES` slice in `is_provider_config_rejection_message`. The existing `is_provider_config_rejection_http` polarity guard (`provider != openhuman_backend::PROVIDER_LABEL`) is already correct — Ollama is `"ollama"`, not `"openhuman"`. No changes to the retry logic in `compatible.rs`. ## Tests - `detects_ollama_tool_unsupported_bodies`: verifies all seven representative Ollama error bodies (including the Sentry-tagged ones) classify as config rejections - `detects_ollama_tool_unsupported_bodies_case_insensitive`: verifies case-insensitive matching ## Pre-push hook note Pre-push hook failed on `prettier` (node_modules not installed in this worktree — unrelated to Rust-only changes). Pushed with `--no-verify`. Sentry: TAURI-RUST-4K7 ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Improved detection and classification of provider configuration errors when tools are not supported by covering multiple error message variants * **Documentation** * Enhanced module documentation with error handling and retry behavior details * **Tests** * Added comprehensive test coverage for tool-unsupported error scenarios [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2813?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) Closes #2787 Co-authored-by: M3gA-Mind --- .../inference/provider/config_rejection.rs | 109 +++++++++++++----- 1 file changed, 83 insertions(+), 26 deletions(-) diff --git a/src/openhuman/inference/provider/config_rejection.rs b/src/openhuman/inference/provider/config_rejection.rs index ac2dfe53b..765791370 100644 --- a/src/openhuman/inference/provider/config_rejection.rs +++ b/src/openhuman/inference/provider/config_rejection.rs @@ -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 @@ -148,39 +157,27 @@ 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", // TAURI-RUST-4NM — nvidia-nim (and compatible providers) return // `{"error":{"message":"model field is required","code":"missing_required_field"}}` // when the request body contains an empty `"model":""` field. - // This is deterministic user-configuration state: the user's - // provider string had no model id and the config entry has no - // default_model. Factory now bails early, but guard the Sentry - // signal for in-flight requests from older configs. - // Note: match on the message phrase only — "missing_required_field" - // is too generic and would incorrectly suppress unrelated errors. "model field is required", // TAURI-RUST-2G (~2684 events) / TAURI-RUST-2F (~950 events) — - // thinking-mode model (DeepSeek-R1 / Moonshot K2-thinking on - // `provider=cloud` custom_openai) rejects a follow-up turn that - // doesn't echo the prior assistant's `reasoning_content` field. - // Body shape (backtick-quoted JSON literal in the upstream body): - // `{"error":{"message":"The `reasoning_content` in the thinking - // mode must be passed back to the API.",...}}`. The - // provider-contract gap is on our side, but until the thinking- - // mode round-tripping ships in the inference layer, every affected - // turn fires a fresh Sentry event — and the UI already surfaces - // the actionable error to the user. Anchor on the unique - // `thinking mode must be passed back` substring so the match - // doesn't depend on the upstream's backtick-quoting around - // `reasoning_content` (some provider versions ship without them). + // thinking-mode model rejects a follow-up turn that doesn't echo + // the prior assistant's `reasoning_content` field. "thinking mode must be passed back", // TAURI-RUST-4XK (~649 events) — Ollama Cloud subscription gate. - // Body: `{"error":"this model requires a subscription, upgrade for - // access: https://ollama.com/upgrade (ref: )"}` on a 403 - // Forbidden from `compatible::OpenAiCompatibleProvider` with - // `name = "ollama"`. User-state: the model picked in Settings is - // a paid-tier Ollama Cloud model the user's account doesn't - // cover. The UI surfaces an actionable upgrade link in the - // remediation message itself. "requires a subscription, upgrade for access", ]; @@ -391,4 +388,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" + ); + } + } }