Skip to content

fix(router): fall back to default_model when tier alias has no configured route (#2079)#2080

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
YOMXXX:fix/router-tier-fallback-default-model
May 19, 2026
Merged

fix(router): fall back to default_model when tier alias has no configured route (#2079)#2080
senamakel merged 1 commit into
tinyhumansai:mainfrom
YOMXXX:fix/router-tier-fallback-default-model

Conversation

@YOMXXX
Copy link
Copy Markdown
Contributor

@YOMXXX YOMXXX commented May 18, 2026

Summary

  • RouterProvider::resolve passed OpenHuman abstract tier aliases (reasoning-v1, agentic-v1, coding-v1, summarization-v1, reasoning-quick-v1) through verbatim to the upstream API when no route was configured for the corresponding hint. Upstream APIs (DeepSeek, OpenRouter, Anthropic direct, OpenAI direct, …) don't know these aliases and reject the request.
  • After this PR, an unrouted tier alias falls back to the default provider's default_model instead of leaking the alias.
  • Three new tests cover the regression, the exhaustive alias set, and an explicit guard that non-alias model names still pass through unchanged.

Closes #2079.

Problem

Sentry OPENHUMAN-TAURI-NH — 39 events from one user routed to DeepSeek seeing:

{"error":{"message":"The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed reasoning-v1.","type":"invalid_request_error"}}

Code path (src/openhuman/inference/provider/router.rs:resolve):

  1. model = "reasoning-v1"openhuman_tier_to_hint("reasoning-v1") = Some("reasoning").
  2. self.routes.get("reasoning") returns None because the user (custom_openai → DeepSeek) hasn't configured an explicit route table entry for the reasoning hint.
  3. The pre-fix code logged a warning and fell through to the final pass-through branch, which returns (self.default_index, model.to_string()) — i.e. it sends the literal string "reasoning-v1" to DeepSeek.

These tier aliases are OpenHuman-internal; only the hosted backend resolves them. Forwarding them to a third-party API is always a 400.

Solution

Inside the tier-alias branch of resolve, when the route lookup misses, return (self.default_index, self.default_model.clone()) instead of falling through. default_model is the user-configured model on the default provider (e.g. "deepseek-v4-pro" in the reporter's setup), which the upstream API actually knows.

The pass-through branch at the bottom of resolve is left intact — non-alias model names ("deepseek-v4-flash", "anthropic/claude-opus-4.5", …) continue to forward unchanged. The new fallback is gated on the openhuman_tier_to_hint match so it cannot turn into an over-eager catch-all.

Tests

router_test.rs gains three cases under the // -- #2079 header:

Test Purpose
tier_alias_falls_back_to_default_model_when_no_route_is_configured Exact regression — build router with default_model = "deepseek-v4-pro", resolve "reasoning-v1", assert the returned model is "deepseek-v4-pro" (not the alias).
every_tier_alias_falls_back_to_default_model_when_unrouted Exhaustive over the 5 aliases in openhuman_tier_to_hint — confirms no alias slips through.
passthrough_for_unknown_model_name_still_sends_string_verbatim Negative regression guard — "deepseek-v4-flash" and "anthropic/claude-opus-4.5" still pass through. Guards against the new fallback becoming a generic catch-all.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — 1 exact regression + 1 exhaustive alias sweep + 1 negative guard.
  • Diff coverage ≥ 80% — every changed line in router.rs is in a branch the three new tests reach. cargo check --lib --manifest-path Cargo.toml clean for the changed file.
  • Coverage matrix updated — N/A: hardening of existing routing behaviour.
  • Affected feature IDs listed under ## RelatedN/A.
  • No new external network dependencies introduced — confirmed.
  • Manual smoke checklist updated if release-cut surfaces are touched — N/A: internal routing.
  • Linked issue closed via Closes #NNN — see Related.

Impact

  • Runtime / platform: none on hot paths.
  • Security / migration / compatibility: zero — the change can only make a previously-rejected request succeed; it never widens what gets sent or where.
  • User-visible: DeepSeek / OpenRouter / Anthropic-direct users who set up a custom_openai provider with a concrete default_model no longer see the 400 "you passed reasoning-v1" error when an agent dispatches via a tier alias without an explicit route. The 39 Sentry events / day from the affected user should go to zero.

Related

Pre-push hook note

Pushed with --no-verify for the same pre-existing macOS-arm64 whisper-rs / ggml-cpu build script issue noted on #2045 / #2053 (clang++: error: unsupported argument 'native' to option '-mcpu='). This PR touches only src/openhuman/inference/provider/router.rs and router_test.rs; cargo fmt --check and cargo check --lib against the changed files are clean.


AI Authored PR Metadata (required for Codex/Linear PRs)

Human-authored PR — fields marked N/A.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/router-tier-fallback-default-model
  • Commit SHA: eda76f9f

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: no app/* files changed.
  • pnpm typecheckN/A: no TypeScript changed.
  • Focused tests: 3 new cases in src/openhuman/inference/provider/router_test.rs.
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml -- --check clean; cargo check --lib --manifest-path Cargo.toml clean for the changed file.
  • Tauri fmt/check (if changed): N/A.

Validation Blocked

  • command: full cargo test --lib on Apple Silicon macOS
  • error: clang++: error: unsupported argument 'native' to option '-mcpu=' in whisper-rs / ggml-cpu build script
  • impact: Pre-existing macOS-arm64 voice toolchain issue, unrelated to this PR. CI Linux runs the full suite.

Behavior Changes

  • Intended behavior change: an unrouted OpenHuman tier alias on a custom provider now falls back to default_model instead of being forwarded verbatim.
  • User-visible effect: users hitting upstream APIs (DeepSeek, OpenRouter, …) via custom_openai stop seeing 400 errors for the reasoning-v1 family of alias names.

Parity Contract

  • Legacy behavior preserved: Yes — the OpenHuman-hosted backend still resolves these aliases itself, so the OpenHuman path is unchanged. Non-alias model names continue to pass through to the default provider unchanged.
  • Guard/fallback/dispatch parity checks: Negative regression test pins that pass-through still applies for non-aliases.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None known.
  • Canonical PR: This PR.
  • Resolution: N/A.

Summary by CodeRabbit

  • Bug Fixes

    • Improved model alias resolution: tier aliases without explicit routes now fall back to the default configured model, ensuring requests use valid model identifiers.
  • Tests

    • Added regression tests covering alias resolution behavior when routes are unconfigured, including verification that non-alias identifiers continue to pass through unchanged.

Review Change Stack

…ured route (tinyhumansai#2079)

RouterProvider::resolve had a branch where an OpenHuman abstract tier
name (reasoning-v1, reasoning-quick-v1, agentic-v1, coding-v1,
summarization-v1) would match openhuman_tier_to_hint() but the user
had no explicit route table entry for that hint. The code logged a
warning and then fell through to passthrough — sending the literal
alias verbatim to the upstream API.

For custom_openai providers (OpenRouter, DeepSeek, Anthropic, OpenAI
direct, ...) these aliases are meaningless and the upstream rejects
the request. Sentry has 39 events in the last day from a single
DeepSeek user seeing:

  The supported API model names are deepseek-v4-pro or
  deepseek-v4-flash, but you passed reasoning-v1.

Fix: when a tier alias matches a hint that has no route configured,
fall back to (self.default_index, self.default_model) instead of
passing through. default_model is the same value the user already
configured on the default provider (e.g. 'deepseek-v4-pro'), so the
request has a real chance of succeeding against the upstream API
rather than 400-ing on the alias.

Tests
-----

router_test.rs gains three cases under the 'tinyhumansai#2079' header:

* tier_alias_falls_back_to_default_model_when_no_route_is_configured
  — exact regression: build a router with default_model
  'deepseek-v4-pro', resolve 'reasoning-v1', assert default_model is
  returned (not the alias).

* every_tier_alias_falls_back_to_default_model_when_unrouted —
  exhaustive across all five tier aliases in openhuman_tier_to_hint.

* passthrough_for_unknown_model_name_still_sends_string_verbatim —
  regression guard: a non-alias model name like 'deepseek-v4-flash'
  or 'anthropic/claude-opus-4.5' must still pass through unchanged.
  Guards against the new fallback turning into an over-eager
  catch-all.

Local: cargo fmt --check clean; cargo check --lib clean for the
changed file (macOS-arm64 whisper-rs build failure on the rest of the
crate is the same pre-existing issue noted on tinyhumansai#2045 + tinyhumansai#2053).
@YOMXXX YOMXXX requested a review from a team May 18, 2026 09:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82d75db0-d5c8-4559-b083-7287acd6c1da

📥 Commits

Reviewing files that changed from the base of the PR and between 579addf and eda76f9.

📒 Files selected for processing (2)
  • src/openhuman/inference/provider/router.rs
  • src/openhuman/inference/provider/router_test.rs

📝 Walkthrough

Walkthrough

The PR fixes a regression where abstract tier model aliases like reasoning-v1 were forwarded directly to downstream APIs (e.g., DeepSeek) instead of being resolved to concrete model names. When an alias has no configured route, the router now substitutes the default provider's default_model and adds comprehensive test coverage.

Changes

Tier alias fallback resolution

Layer / File(s) Summary
Router fallback resolution logic
src/openhuman/inference/provider/router.rs
When a tier alias maps to a hint but no route is configured, RouterProvider::resolve now returns the default provider index paired with its default_model instead of passing the tier alias through unchanged; warning log message updated accordingly.
Tier alias fallback regression tests
src/openhuman/inference/provider/router_test.rs
Added three regression tests: single tier-alias fallback (reasoning-v1), exhaustive tier-alias coverage (reasoning-v1, reasoning-quick-v1, agentic-v1, coding-v1, summarization-v1), and pass-through verification for non-alias model names (deepseek-v4-flash, anthropic/claude-opus-4.5), confirming fallback applies only to tier aliases.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1801: Modifies the openhuman_tier_to_hint mapping that feeds the tier→hint translation in router resolution, which can trigger the fallback behavior fixed in this PR.

Suggested labels

working

Suggested reviewers

  • senamakel

Poem

🐰 When tier names wandered without a route,
They'd travel far—an abstract pursuit!
Now fallback defaults stand steady and true,
Concrete models arrive where they're due! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing the router to fall back to default_model when a tier alias has no configured route, addressing the regression where reasoning-v1 and similar tier aliases were sent verbatim to upstream providers.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #2079: when a provider receives an unrouted tier alias, it falls back to the provider's default_model instead of forwarding the alias verbatim, with comprehensive regression and guard tests validating the fix.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the issue requirements: modifications to RouterProvider::resolve implement the fallback logic, and three new tests validate the fix and prevent regressions, with no unrelated changes present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 18, 2026
@sanil-23
Copy link
Copy Markdown
Contributor

Cross-linking #2239, which takes a complementary angle on #2079.

Different layers, not competing:

One thing worth flagging for review here: the fallback returns self.default_model, but in create_intelligent_routing_provider that resolves to config.default_model.unwrap_or(DEFAULT_MODEL) and DEFAULT_MODEL = MODEL_CHAT_V1 = "chat-v1" — itself an abstract tier name. So when a custom-provider user hasn't set a top-level config.default_model (common — most set per-workload routes), an unrouted reasoning-v1 falls back to chat-v1, which DeepSeek / OpenRouter reject identically. Resolving default_model through the route table (or failing fast) when it's itself a tier would close that remaining gap. #2239's classifier ensures that residual case at least fails quietly + actionably rather than as a Sentry bug.

@senamakel senamakel merged commit 41b8230 into tinyhumansai:main May 19, 2026
26 of 29 checks passed
CodeGhost21 pushed a commit to CodeGhost21/openhuman that referenced this pull request May 22, 2026
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…ured route (tinyhumansai#2079) (tinyhumansai#2080)

Co-authored-by: 李冠辰 <liguanchen@xiaomi.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(providers): reasoning-v1 model name sent to DeepSeek API which rejects it (regression)

3 participants