Skip to content

feat: support models dict format in custom_providers#532

Open
happy5318 wants to merge 1 commit intonesquena:masterfrom
happy5318:feature/custom-providers-models-dict
Open

feat: support models dict format in custom_providers#532
happy5318 wants to merge 1 commit intonesquena:masterfrom
happy5318:feature/custom-providers-models-dict

Conversation

@happy5318
Copy link
Copy Markdown

@happy5318 happy5318 commented Apr 15, 2026

Summary

This PR adds support for the models dict format in custom_providers configuration, which is used by Hermes Agent to define multiple models per custom provider.

Problem

Currently, the WebUI only supports the single model field format in custom_providers:

custom_providers:
- name: my-provider
  model: "single-model"

But Hermes Agent also supports a models dict format for defining multiple models:

custom_providers:
- name: my-provider
  models:
    model-a:
      context_length: 128000
    model-b:
      context_length: 256000

Solution

  • Parse both model (single) and models (dict) formats
  • Each custom provider displays as a separate group in the model dropdown (e.g., "Custom:ProviderName")
  • Fix duplicate "Custom" group issue when active_provider is in custom:xxx format

Changes

  • Modified get_available_models() in api/config.py
  • Added _custom_provider_models dict to collect models per provider
  • Added logic to skip custom:xxx providers in the main loop (avoid duplicates)
  • Added separate group creation for each custom provider

Testing

Verified with a config containing multiple custom providers with 20+ models total, all displayed correctly in the WebUI model dropdown.

Backward Compatibility

✅ Maintains full backward compatibility with the single model field format

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Review: feat — support models dict format in custom_providers

This is a clean, well-scoped fix for a real config compatibility gap. The implementation is correct.

What the PR does

api/config.pyget_available_models():

  1. Loops through custom_providers, now handles both:
    • model: "single-model" (legacy/single format)
    • models: {model-a: {context_length: ...}, model-b: {...}} (new dict format)
  2. Collects all models per provider into _custom_provider_models[provider_name]
  3. Adds each custom provider as a separate dropdown group ("Custom:ProviderName")
  4. Fixes the duplicate group issue when active_provider is "custom:xxx" format

Notes

  • De-duplication via _seen_custom_ids — correctly prevents the same model ID from appearing in both the auto-detected list and a custom provider group. ✅
  • Backward compatibility — the old model: single-field path still flows through and gets picked up into _provider_models, then added to _custom_provider_models. No existing config breaks. ✅
  • _is_custom_subprovider guard — the detected_providers.discard("custom") logic was already tricky; the new condition correctly handles custom:xxx active_provider format to avoid an empty "Custom" group when real custom:xxx groups are rendered separately. ✅
  • else:if not groups: — the change from an else on the if detected_providers: block to a standalone if not groups: is correct. Without this, the fallback model would never appear when only custom providers are configured (since detected_providers could be empty while groups gets populated by the custom provider block above). ✅

Minor observation

When both models dict and model single field are present on the same provider entry, the current code will include both. This is probably fine (union of all declared models) but could lead to duplicates if the same model ID appears in both — the _seen_custom_ids guard prevents cross-provider duplicates but not within-provider ones. Not a blocker for this PR, just worth noting if edge-case robustness matters later.

Verdict

Ready to merge. The fix is targeted (one file, ~60 lines net), well-tested, and closes a real config parity gap between the CLI and WebUI. 🟢

@happy5318 happy5318 force-pushed the feature/custom-providers-models-dict branch from eec84e8 to 7b2717e Compare April 15, 2026 14:26
- Parse 'models' dict format: {model-name: {context_length: ...}}
- Parse 'models' list format: [model-name1, model-name2, ...]
- Each custom provider displays as separate group in dropdown
- Fix duplicate 'Custom' group when active_provider is 'custom:xxx'
- Fix cross-provider deduplication bug (same model in different providers)
- Maintain backward compatibility with single 'model' field format
@happy5318 happy5318 force-pushed the feature/custom-providers-models-dict branch from 7b2717e to cdc1ded Compare April 15, 2026 14:26
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Agent Review — HOLD 🔶

Thank you @happy5318 for this feature — supporting a models: {} dict in custom_providers is a great improvement for users with many models per provider. The routing logic and per-provider dropdown grouping are well designed.

Three items to address before this can merge:


1. Missing tests (required)

No tests were added for the new dict format path. Please add tests to tests/test_model_resolver.py (or a new file) covering:

a) Dict format routes correctly:

def test_custom_provider_models_dict_routing():
    # @provider:model emitted by dropdown resolves to correct base_url
    # model-a from 'Local Ollama' provider returns base_url='http://localhost:11434/v1'

b) Multiple providers don't cross-route:

def test_custom_provider_multiple_no_cross_routing():
    # @provider-two:model-b should return base_url of provider-two, not provider-one

c) Each provider gets its own display group:

def test_get_available_models_dict_format_groups():
    # Two custom_providers entries → two separate 'Custom:ProviderName' groups in the dropdown

2. Double group regression (required fix)

Users who have both a configured external provider and custom_providers entries will see a duplicate empty "Custom" group in the dropdown alongside their "Custom:ProviderName" groups.

Root cause: the condition that discards "custom" from active_provider only applies when _is_custom_subprovider is True, but _has_custom_providers is not checked. When _has_custom_providers is True and we're already going to render per-provider groups, "custom" should also be discarded from the main loop.


3. models: [list] asymmetry (minor, but worth fixing)

get_available_models() accepts models: [model-a, model-b] as a list and will show these in the dropdown. But resolve_model_provider() only handles the dict case — a model from a list-format entry will not route through the custom provider's base_url.

Either remove the list-format branch from get_available_models() so we don't surface models that don't route correctly, or add the parallel list-check to resolve_model_provider().


Once those three are addressed this will be ready to merge. Happy to review quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants