Skip to content

fix(observability): classify 'does not support tools' provider 400 as user-state (Sentry TAURI-RUST-35 + 10 siblings)#2796

Open
CodeGhost21 wants to merge 2 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-35-does-not-support-tools
Open

fix(observability): classify 'does not support tools' provider 400 as user-state (Sentry TAURI-RUST-35 + 10 siblings)#2796
CodeGhost21 wants to merge 2 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-35-does-not-support-tools

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 27, 2026

Summary

Add "does not support tools" to is_provider_config_rejection_message's PHRASES array (src/openhuman/inference/provider/config_rejection.rs). The user picks a model that doesn't implement tool calling, the agent harness sends a tool spec anyway, and the upstream rejects with {"error":{"message":"<model id> does not support tools",...}}. Pure user-config — Sentry has no actionable path.

Why this drops 11 Sentry issues, not 1

Because the rejection message includes the literal model id, each (provider prefix × model id) combination produced a distinct Sentry fingerprint. The same root cause is currently split across 11 unresolved Sentry issues totalling ~459 events (snapshot 2026-05-28):

shortId events provider prefix
TAURI-RUST-35 307 cloud (gemma3:1b-it-qat)
TAURI-RUST-DF 83 cloud
TAURI-RUST-123 25 cloud
TAURI-RUST-4K7 19 ollama
TAURI-RUST-4FS 10 cloud
TAURI-RUST-4F6 5 cloud
TAURI-RUST-2YA 4 cloud
TAURI-RUST-4KR 3 ollama
TAURI-RUST-4KH 1 cloud
TAURI-RUST-4KY 1 ollama
TAURI-RUST-4Z0 1 ollama (deepseek-r1:8b, type=api_error)

Anchor token: the exact substring "does not support tools". It's stable across every observed wrapper shape (cloud / ollama / custom_openai × streaming API error / API error), every observed model id, and both observed type tokens (invalid_request_error and api_error — TAURI-RUST-4Z0). A single phrase covers the whole long tail and any future model that hits the same upstream rejection class.

The matcher upstream is already case-insensitive (body.to_ascii_lowercase() before substring search), so capitalisation variants are covered for free.

Tests added (provider::config_rejection::tests)

  • detects_does_not_support_tools_family — 5 verbatim wire shapes: TAURI-RUST-35 (cloud, gemma3), TAURI-RUST-4K7 (ollama prefix), the non-streaming API error sibling, a bare body without the wrapper, and TAURI-RUST-4Z0 (ollama / deepseek-r1:8b with the distinct type:"api_error" envelope — pins that the matcher never requires a specific type token).
  • does_not_classify_unrelated_tools_phrases_as_config_rejection — polarity guard pinning near-miss phrases that must STILL escalate to Sentry (real tool execution failures, generic "tools" mentions, reversed phrasing).

Test plan

  • cargo test detects_does_not_support_tools_family — passes (5 wire shapes)
  • cargo test does_not_classify_unrelated_tools_phrases — passes (polarity)
  • cargo test config_rejection — 8 tests pass, 0 regressions
  • cargo check --manifest-path Cargo.toml --bin openhuman-core — passes
  • cargo fmt --check — clean

Post-merge observation: all 11 listed Sentry issues should drop to ~0 events on releases ≥ this fix. Any new "does not support tools" event on a newer release means the matcher missed a wrapper variant — investigate the wire shape, not the model id.

… user-state (Sentry TAURI-RUST-35 + 9 siblings)

Add `"does not support tools"` to the `is_provider_config_rejection_message`
PHRASES array. The user picks a model that doesn't implement tool calling
(e.g. `gemma3:1b-it-qat`, `qwen2.5:0.5b`, `phi3.5:mini`), the agent
harness sends a tool spec anyway, and the upstream rejects with
`{"error":{"message":"<model id> does not support tools",
"type":"invalid_request_error",…}}`. Pure user-config error — the
remediation is "pick a tool-capable model in Settings → AI → LLM";
Sentry has no actionable path.

One body substring drops the entire long-tail family. Because the error
includes the model id, each distinct (provider prefix × model id) combo
produced its own Sentry fingerprint — currently 10 unresolved sibling
issues totaling ~458 events on the latest production release as of
2026-05-28:

| shortId         | events | provider |
|-----------------|--------|----------|
| TAURI-RUST-35   | 307    | cloud    |
| TAURI-RUST-DF   | 83     | cloud    |
| TAURI-RUST-123  | 25     | cloud    |
| TAURI-RUST-4K7  | 19     | ollama   |
| TAURI-RUST-4FS  | 10     | cloud    |
| TAURI-RUST-4F6  | 5      | cloud    |
| TAURI-RUST-2YA  | 4      | cloud    |
| TAURI-RUST-4KR  | 3      | ollama   |
| TAURI-RUST-4KH  | 1      | cloud    |
| TAURI-RUST-4KY  | 1      | ollama   |

The anchor is the exact `"does not support tools"` substring — stable
across all observed wrapper shapes (`cloud` / `ollama` / `custom_openai`
× `streaming API error` / `API error`) and across all observed model
ids. The matcher is already case-insensitive (`body.to_ascii_lowercase()`
upstream), so capitalisation variants are covered for free.

Tests added in `provider::config_rejection::tests`:
- `detects_does_not_support_tools_family` — verbatim TAURI-RUST-35 wire
  shape (cloud + ollama prefix + non-streaming sibling + bare body
  variants).
- `does_not_classify_unrelated_tools_phrases_as_config_rejection` —
  polarity guard pinning near-miss phrases that must STILL escalate
  (real tool execution failures, generic "tools" mentions, reversed
  phrasing).

## Test plan
- [x] `cargo test detects_does_not_support_tools_family` — passes (4 wire shapes)
- [x] `cargo test does_not_classify_unrelated_tools_phrases` — passes (polarity)
- [x] `cargo test config_rejection` — 8 tests pass, 0 regressions
- [x] `cargo check --bin openhuman-core` — passes
- [x] `cargo fmt --check` — clean
@CodeGhost21 CodeGhost21 requested a review from a team May 27, 2026 21:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

Extended is_provider_config_rejection_message to detect tool-calling capability mismatches by recognizing "does not support tools" error messages. Added comprehensive test coverage for the new classifier across multiple wrapper shapes, providers, and negative cases.

Changes

Provider Config Rejection Classifier Enhancement

Layer / File(s) Summary
Tool-calling capability rejection detection
src/openhuman/inference/provider/config_rejection.rs
Added "does not support tools" substring match to is_provider_config_rejection_message to classify tool-calling model capability mismatch errors as provider config-rejections.
Test coverage for does-not-support-tools family
src/openhuman/inference/provider/config_rejection.rs
New unit tests validate classifier recognizes "does not support tools" across multiple wrapper shapes and providers, and confirm unrelated tools-mentioning bodies and malformed messages do not match.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2309: Both PRs update the same provider-config rejection classifier (is_provider_config_rejection_message) in src/openhuman/inference/provider/config_rejection.rs by extending its case-insensitive substring match arms (and corresponding tests) for additional Wave 4 rejection-message variants.
  • tinyhumansai/openhuman#2346: The main PR expands the underlying config-rejection message classifier (is_provider_config_rejection_message) to match "does not support tools", which would directly affect when the retrieved PR's streaming/chat HTTP paths (compatible.rs) detect config-rejection responses and log/suppress them instead of falling back to generic provider-error reporting.
  • tinyhumansai/openhuman#2239: Both PRs extend the same provider config-rejection classifier (is_provider_config_rejection_message)—the main PR adds the "does not support tools" phrase family, while the retrieved PR wires that classifier into observability/ops/web handling—so the main PR directly builds on the retrieved PR's core predicate.

Suggested labels

working, bug

Suggested reviewers

  • graycyrus
  • senamakel

Poem

🐰 A model without tools is quite a sight,
Now caught and handled, swift and tight,
Each wrapper shape, each provider too,
Tests confirm what phrases do.
Tool-calling dreams now match just right! 🛠️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and clearly identifies the main change: classifying 'does not support tools' provider errors as user-state issues, with specific reference to the Sentry ticket. This matches the core objective of adding detection for tool-calling capability mismatches in the config_rejection module.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…or does-not-support-tools

Sentry issue 5664 (TAURI-RUST-4Z0, ollama/deepseek-r1:8b) is the 11th
fingerprint of the same "does not support tools" root cause this PR
classifies. Its envelope carries `"type":"api_error"` rather than the
`"invalid_request_error"` seen in the other siblings — add it as a test
case so the matcher can never be narrowed to require a specific `type`
token. The `"does not support tools"` body substring stays the only
anchor.

No production code change — the existing PHRASES entry already matches
this body (verified). Test-only strengthening.
@coderabbitai coderabbitai Bot added working A PR that is being worked on by the team. bug labels May 28, 2026
@CodeGhost21 CodeGhost21 changed the title fix(observability): classify 'does not support tools' provider 400 as user-state (Sentry TAURI-RUST-35 + 9 siblings) fix(observability): classify 'does not support tools' provider 400 as user-state (Sentry TAURI-RUST-35 + 10 siblings) May 28, 2026
graycyrus
graycyrus previously approved these changes May 28, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. Single additive change to the PHRASES array in is_provider_config_rejection_message — the anchor phrase "does not support tools" is stable across every observed provider prefix (cloud / ollama / custom_openai), wrapper shape (streaming / non-streaming), and type token (invalid_request_error / api_error). The existing body.to_ascii_lowercase() call means capitalisation variants are free.

Test coverage is thorough: 5 verbatim wire shapes pin each meaningful variant, and the polarity guard correctly rejects near-miss phrases that must still reach Sentry. The diff-cover gate passed, all Rust core tests green, fmt + clippy clean.

Breaking risk is zero — purely additive. No API, type, or schema changes. The 11 Sentry issues (~459 events) should drain to zero on the next release.

@graycyrus graycyrus dismissed their stale review May 28, 2026 05:19

Auto-corrected: reviewer policy requires COMMENT state, not APPROVE. Clean PR moved to manual approval queue.

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. Single additive change to the PHRASES array in is_provider_config_rejection_message — the anchor phrase "does not support tools" is stable across every observed provider prefix (cloud / ollama / custom_openai), wrapper shape (streaming / non-streaming), and type token (invalid_request_error / api_error). The existing body.to_ascii_lowercase() call means capitalisation variants are free.

Test coverage is thorough: 5 verbatim wire shapes pin each meaningful variant, and the polarity guard correctly rejects near-miss phrases that must still reach Sentry. The diff-cover gate passed, all Rust core tests green, fmt + clippy clean.

Breaking risk is zero — purely additive. No API, type, or schema changes. The 11 Sentry issues (~459 events) should drain to zero on the next release.

✅ Ready for approval.

@oxoxDev oxoxDev self-assigned this May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants