Skip to content

fix(auxiliary): resolve named custom providers and 'main' alias in auxiliary routing#5978

Merged
teknium1 merged 3 commits intomainfrom
hermes/hermes-c7eda492
Apr 8, 2026
Merged

fix(auxiliary): resolve named custom providers and 'main' alias in auxiliary routing#5978
teknium1 merged 3 commits intomainfrom
hermes/hermes-c7eda492

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

@teknium1 teknium1 commented Apr 8, 2026

Summary

Fixes auxiliary task routing (vision, compression, web_extract, session_search, etc.) for users on named custom providers.

Bug: Setting auxiliary.vision.provider: main or auxiliary.vision.provider: beans (a named custom provider from custom_providers in config.yaml) fails with:

RuntimeError: No LLM provider configured for task=vision provider=main. Run: hermes setup

Root cause — two bugs in auxiliary_client.py:

  1. main alias hardcoded to "custom" — only checks legacy OPENAI_BASE_URL env vars, missing the user's actual provider config entirely
  2. Named custom providers unrecognized — resolution chain jumps to PROVIDER_REGISTRY.get("beans") → None → "unknown provider", never consulting custom_providers from config.yaml

Fix — 3 targeted changes (+34 lines):

Location Change
resolve_provider_client() main alias Read _read_main_provider() → resolve to actual provider name
resolve_provider_client() before PROVIDER_REGISTRY Try _get_named_custom_provider() for config.yaml entries
_normalize_vision_provider() main alias Same main → actual provider resolution for vision path

Test results

  • 13 new unit tests covering both bugs + edge cases
  • 96 existing auxiliary client tests: all pass
  • E2E verification with isolated HERMES_HOME confirms both provider: main and provider: beans resolve correctly

Related PRs

This fix is more comprehensive and handles both bugs in a single change.

Reported by Laura via Discord.

dlkakbs and others added 3 commits April 7, 2026 14:01
…e match

Captions in photo bursts and media group albums were silently dropped when
a shorter caption happened to be a substring of an existing one (e.g.
"Meeting" lost inside "Meeting agenda"). Extract a shared _merge_caption
static helper that splits on "\n\n" and uses exact match with whitespace
normalisation, then use it in both _enqueue_photo_event and
_queue_media_group_event.

Adds 13 unit tests covering the fixed bug scenarios.

Cherry-picked from PR #2671 by Dilee.
Move _merge_caption helper from TelegramAdapter to BasePlatformAdapter
so all adapters inherit it. Fix the same substring-containment bug in:
- gateway/platforms/base.py (photo burst merging)
- gateway/run.py (priority photo follow-up merging)
- gateway/platforms/feishu.py (media batch merging)

The original fix only covered telegram.py. The same bug existed in base.py
and run.py (pure substring check) and feishu.py (list membership without
whitespace normalization).
…xiliary routing

Two bugs caused auxiliary tasks (vision, compression, etc.) to fail when
using named custom providers defined in config.yaml:

1. 'provider: main' was hardcoded to 'custom', which only checks legacy
   OPENAI_BASE_URL env vars. Now reads _read_main_provider() to resolve
   to the actual provider (e.g., 'custom:beans', 'openrouter', 'deepseek').

2. Named custom provider names (e.g., 'beans') fell through to
   PROVIDER_REGISTRY which doesn't know about config.yaml entries.
   Now checks _get_named_custom_provider() before the registry fallback.

Fixes both resolve_provider_client() and _normalize_vision_provider()
so the fix covers all auxiliary tasks (vision, compression, web_extract,
session_search, etc.).

Adds 13 unit tests. Reported by Laura via Discord.
@teknium1 teknium1 force-pushed the hermes/hermes-c7eda492 branch from e7529fb to 25b6797 Compare April 8, 2026 00:58
@teknium1 teknium1 merged commit cbf1f15 into main Apr 8, 2026
3 of 5 checks passed
teknium1 pushed a commit that referenced this pull request Apr 8, 2026
Users on non-aggregator providers (DeepSeek, Alibaba, ZAI, Gemini, etc.)
had vision silently fail because resolve_vision_provider_client()'s auto
path only iterates _VISION_AUTO_PROVIDER_ORDER (openrouter, nous, codex,
anthropic, custom). Their main provider was never tried.

Mirror the same non-aggregator main-provider-first check that
_resolve_auto() already has for non-vision tasks. Combined with the
named custom provider fix in #5978, this also covers users whose main
provider is a named custom endpoint.

Cherry-picked from PR #5376 by Mibay. Closes #5366.
teknium1 pushed a commit that referenced this pull request Apr 8, 2026
…llback

Users on non-aggregator providers (DeepSeek, Alibaba, ZAI, Gemini, etc.)
had vision silently fail because resolve_vision_provider_client()'s auto
path only iterates _VISION_AUTO_PROVIDER_ORDER (openrouter, nous, codex,
anthropic, custom). Their main provider was never tried.

Mirror the same non-aggregator main-provider-first check that
_resolve_auto() already has for non-vision tasks. Combined with the
named custom provider fix in #5978, this also covers users whose main
provider is a named custom endpoint.

Add vision-specific API fallback in call_llm: if the auto-resolved main
provider returns 400/422 (model doesn't support vision), fall back through
the strict vision backends which use known vision-capable models. This
handles the case where the user's main model is text-only.

Cherry-picked from PR #5376 by Mibay. Closes #5366.
teknium1 pushed a commit that referenced this pull request Apr 8, 2026
…tive provider

Simplify the vision auto-detection chain from 5 backends (openrouter,
nous, codex, anthropic, custom) down to 3:

  1. OpenRouter  (known vision-capable default model)
  2. Nous Portal (known vision-capable default model)
  3. Active provider + model (whatever the user is running)
  4. Stop

This is simpler and more predictable. The active provider step uses
resolve_provider_client() which handles all provider types including
named custom providers (from #5978).

Removed the complex preferred-provider promotion logic and API-level
fallback — the chain is short enough that it doesn't need them.

Based on PR #5376 by Mibay. Closes #5366.
teknium1 pushed a commit that referenced this pull request Apr 8, 2026
…tive provider

Simplify the vision auto-detection chain from 5 backends (openrouter,
nous, codex, anthropic, custom) down to 3:

  1. OpenRouter  (known vision-capable default model)
  2. Nous Portal (known vision-capable default model)
  3. Active provider + model (whatever the user is running)
  4. Stop

This is simpler and more predictable. The active provider step uses
resolve_provider_client() which handles all provider types including
named custom providers (from #5978).

Removed the complex preferred-provider promotion logic and API-level
fallback — the chain is short enough that it doesn't need them.

Based on PR #5376 by Mibay. Closes #5366.
jooray added a commit to jooray/hermes-agent that referenced this pull request Apr 8, 2026
* upstream/main: (245 commits)
  fix: preserve existing thresholds, remove pre-read byte guard
  fix(tools): address PR review — remove _extract_raw_output, BudgetConfig everywhere, read_file hardening
  feat(budget): make tool result persistence thresholds configurable
  wip: tool result fixes -- persistence
  fix(minimax): correct context lengths, model catalog, thinking guard, aux model, and config base_url
  fix(vision): simplify vision auto-detection to openrouter → nous → active provider
  feat(agent): add jittered retry backoff
  feat(cron): track delivery failures in job status (NousResearch#6042)
  feat(feishu): add interactive card approval buttons (NousResearch#6043)
  fix: provider/model resolution — salvage 4 PRs + MiniMax aux URL fix (NousResearch#5983)
  feat: use mimo-v2-pro for non-vision auxiliary tasks on Nous free tier (NousResearch#6018)
  feat(tools): add "no_mcp" sentinel to exclude MCP servers per platform
  fix: use camelCase structuredContent attr, prefer structured over text
  test(mcp): add structured_content preservation tests
  fix(mcp): preserve structured_content in tool call results
  fix(auxiliary): resolve named custom providers and 'main' alias in auxiliary routing (NousResearch#5978)
  fix: CLI/UX batch — ChatConsole errors, curses scroll, skin-aware banner, git state banner (NousResearch#5974)
  fix: add _profile_arg tests + move STT language to config.yaml
  fix(discord): discard empty placeholder on voice transcription + force STT language
  fix(gateway): discard empty placeholder when voice transcription succeeds
  ...

# Conflicts:
#	cron/scheduler.py
#	hermes_cli/tools_config.py
#	toolsets.py
DiscoStew6082 pushed a commit to DiscoStew6082/hermes-agent that referenced this pull request Apr 9, 2026
…xiliary routing (NousResearch#5978)

* fix(telegram): replace substring caption check with exact line-by-line match

Captions in photo bursts and media group albums were silently dropped when
a shorter caption happened to be a substring of an existing one (e.g.
"Meeting" lost inside "Meeting agenda"). Extract a shared _merge_caption
static helper that splits on "\n\n" and uses exact match with whitespace
normalisation, then use it in both _enqueue_photo_event and
_queue_media_group_event.

Adds 13 unit tests covering the fixed bug scenarios.

Cherry-picked from PR NousResearch#2671 by Dilee.

* fix: extend caption substring fix to all platforms

Move _merge_caption helper from TelegramAdapter to BasePlatformAdapter
so all adapters inherit it. Fix the same substring-containment bug in:
- gateway/platforms/base.py (photo burst merging)
- gateway/run.py (priority photo follow-up merging)
- gateway/platforms/feishu.py (media batch merging)

The original fix only covered telegram.py. The same bug existed in base.py
and run.py (pure substring check) and feishu.py (list membership without
whitespace normalization).

* fix(auxiliary): resolve named custom providers and 'main' alias in auxiliary routing

Two bugs caused auxiliary tasks (vision, compression, etc.) to fail when
using named custom providers defined in config.yaml:

1. 'provider: main' was hardcoded to 'custom', which only checks legacy
   OPENAI_BASE_URL env vars. Now reads _read_main_provider() to resolve
   to the actual provider (e.g., 'custom:beans', 'openrouter', 'deepseek').

2. Named custom provider names (e.g., 'beans') fell through to
   PROVIDER_REGISTRY which doesn't know about config.yaml entries.
   Now checks _get_named_custom_provider() before the registry fallback.

Fixes both resolve_provider_client() and _normalize_vision_provider()
so the fix covers all auxiliary tasks (vision, compression, web_extract,
session_search, etc.).

Adds 13 unit tests. Reported by Laura via Discord.

---------

Co-authored-by: Dilee <uzmpsk.dilekakbas@gmail.com>
DiscoStew6082 pushed a commit to DiscoStew6082/hermes-agent that referenced this pull request Apr 9, 2026
…tive provider

Simplify the vision auto-detection chain from 5 backends (openrouter,
nous, codex, anthropic, custom) down to 3:

  1. OpenRouter  (known vision-capable default model)
  2. Nous Portal (known vision-capable default model)
  3. Active provider + model (whatever the user is running)
  4. Stop

This is simpler and more predictable. The active provider step uses
resolve_provider_client() which handles all provider types including
named custom providers (from NousResearch#5978).

Removed the complex preferred-provider promotion logic and API-level
fallback — the chain is short enough that it doesn't need them.

Based on PR NousResearch#5376 by Mibay. Closes NousResearch#5366.
dbmizrahi pushed a commit to dbmizrahi/hermes-agent that referenced this pull request Apr 10, 2026
…xiliary routing (NousResearch#5978)

* fix(telegram): replace substring caption check with exact line-by-line match

Captions in photo bursts and media group albums were silently dropped when
a shorter caption happened to be a substring of an existing one (e.g.
"Meeting" lost inside "Meeting agenda"). Extract a shared _merge_caption
static helper that splits on "\n\n" and uses exact match with whitespace
normalisation, then use it in both _enqueue_photo_event and
_queue_media_group_event.

Adds 13 unit tests covering the fixed bug scenarios.

Cherry-picked from PR NousResearch#2671 by Dilee.

* fix: extend caption substring fix to all platforms

Move _merge_caption helper from TelegramAdapter to BasePlatformAdapter
so all adapters inherit it. Fix the same substring-containment bug in:
- gateway/platforms/base.py (photo burst merging)
- gateway/run.py (priority photo follow-up merging)
- gateway/platforms/feishu.py (media batch merging)

The original fix only covered telegram.py. The same bug existed in base.py
and run.py (pure substring check) and feishu.py (list membership without
whitespace normalization).

* fix(auxiliary): resolve named custom providers and 'main' alias in auxiliary routing

Two bugs caused auxiliary tasks (vision, compression, etc.) to fail when
using named custom providers defined in config.yaml:

1. 'provider: main' was hardcoded to 'custom', which only checks legacy
   OPENAI_BASE_URL env vars. Now reads _read_main_provider() to resolve
   to the actual provider (e.g., 'custom:beans', 'openrouter', 'deepseek').

2. Named custom provider names (e.g., 'beans') fell through to
   PROVIDER_REGISTRY which doesn't know about config.yaml entries.
   Now checks _get_named_custom_provider() before the registry fallback.

Fixes both resolve_provider_client() and _normalize_vision_provider()
so the fix covers all auxiliary tasks (vision, compression, web_extract,
session_search, etc.).

Adds 13 unit tests. Reported by Laura via Discord.

---------

Co-authored-by: Dilee <uzmpsk.dilekakbas@gmail.com>
dbmizrahi pushed a commit to dbmizrahi/hermes-agent that referenced this pull request Apr 10, 2026
…tive provider

Simplify the vision auto-detection chain from 5 backends (openrouter,
nous, codex, anthropic, custom) down to 3:

  1. OpenRouter  (known vision-capable default model)
  2. Nous Portal (known vision-capable default model)
  3. Active provider + model (whatever the user is running)
  4. Stop

This is simpler and more predictable. The active provider step uses
resolve_provider_client() which handles all provider types including
named custom providers (from NousResearch#5978).

Removed the complex preferred-provider promotion logic and API-level
fallback — the chain is short enough that it doesn't need them.

Based on PR NousResearch#5376 by Mibay. Closes NousResearch#5366.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants