Skip to content

Dashboard model switcher falls back to openrouter for custom_providers #57143

Description

@Miaomao27

Summary

When using the Hermes Dashboard to switch the default model to a custom provider (e.g. SiliconFlow), the selection automatically reverts to openrouter. The CLI method (hermes config set) works correctly.

Environment

  • Hermes Agent version: 0.18.0 (2026.7.1)
  • OS: Linux (Ubuntu)

Steps to Reproduce

  1. Add a custom provider in ~/.hermes/config.yaml:
custom_providers:
  - name: siliconflow
    base_url: https://api.siliconflow.cn/v1
    api_key: sk-xxx
    api_mode: chat_completions
    model: deepseek-ai/DeepSeek-V4-Flash
    models:
      deepseek-ai/DeepSeek-V4-Flash:
        name: deepseek-ai/DeepSeek-V4-Flash
  1. Open Hermes Dashboard → Models page
  2. Select the custom provider (siliconflow) and model (deepseek-ai/DeepSeek-V4-Flash)
  3. Click "Set as Main Model" or use the model switcher

Expected: Model switches to siliconflow / deepseek-ai/DeepSeek-V4-Flash

Actual: Model reverts to openrouter

Root Cause Analysis

The issue is in hermes_cli/web_server.py, function _normalize_main_model_assignment (around line 964-1028):

def _normalize_main_model_assignment(provider: str, model: str) -> tuple[str, str]:
    canonical = normalize_provider(prov_in)

    if canonical not in _KNOWN_PROVIDER_NAMES and "/" in model_in:
        # Vendor prefix posing as a provider (analytics fallback).
        # ... otherwise default to openrouter.
        canonical = "openrouter"
        prov_in = "openrouter"

_KNOWN_PROVIDER_NAMES only includes built-in providers. Custom providers defined in config.yaml are not recognized, so when the dashboard sends provider="siliconflow" with model="deepseek-ai/DeepSeek-V4-Flash", the function treats it as an unknown vendor prefix and forces fallback to openrouter.

Suggested Fix

Check custom_providers from config before falling back to openrouter:

def _normalize_main_model_assignment(provider: str, model: str) -> tuple[str, str]:
    from hermes_cli.models import _KNOWN_PROVIDER_NAMES, normalize_provider

    prov_in = (provider or "").strip()
    model_in = (model or "").strip()
    canonical = normalize_provider(prov_in)

    # Check if provider exists in custom_providers config
    try:
        cfg = load_config()
        custom_providers = cfg.get("custom_providers", [])
        custom_provider_names = {cp.get("name") for cp in custom_providers if cp.get("name")}
    except Exception:
        custom_provider_names = set()

    # If provider is a known custom provider, keep it verbatim
    if canonical in custom_provider_names:
        return prov_in, model_in

    if canonical not in _KNOWN_PROVIDER_NAMES and "/" in model_in:
        # ... existing fallback logic

Workaround

Use CLI instead of Dashboard:

hermes config set model.provider siliconflow
hermes config set model.default deepseek-ai/DeepSeek-V4-Flash

Additional Context

This affects all custom providers, not just SiliconFlow. Any user who defines a custom OpenAI-compatible endpoint in custom_providers will encounter this issue when trying to switch models via the Dashboard UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/configConfig system, migrations, profilescomp/cliCLI entry point, hermes_cli/, setup wizardcomp/dashboardWeb dashboard / control panel UI (dashboard/, landing)sweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradestype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions