hermes - ✅(Solved) Fix deepseek provider: model normalization and base_url override break custom endpoints (e.g. Volcengine ARK) [1 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#17199Fetched 2026-04-29 06:36:46
View on GitHub
Comments
2
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
labeled ×5commented ×2cross-referenced ×1

Root Cause

Bug 1: `_normalize_for_deepseek()` in `hermes_cli/model_normalize.py` has a fixed allowlist of only two model names. It has no mechanism to pass through versioned IDs or custom endpoint model names. The warning is emitted but cannot be suppressed.

Bug 2: The `_resolve_provider_credentials_from_entry()` path in `runtime_provider.py` checks `pool_url_is_default` to decide whether to apply `model.base_url` from config. However, the pool entry's `base_url` is already hardcoded to the DeepSeek default when built from `DEEPSEEK_API_KEY` env var, so `pool_url_is_default = True` and `config.yaml` base_url IS applied — but only if the pool entry hasn't been marked `exhausted` from a prior failed attempt. Once exhausted, the entry is skipped entirely and `config.yaml` base_url is never consulted.

Fix Action

Fixed

PR fix notes

PR #17217: fix: pass through Volcengine ARK endpoint IDs (ep-*) in DeepSeek normalization

Description (problem / solution / changelog)

Problem

When using a custom DeepSeek-compatible endpoint (e.g. Volcengine ARK at ark.cn-beijing.volces.com/api/v3), custom endpoint model IDs like ep-20250428-xxxxx are silently normalized to deepseek-chat, causing HTTP 404 errors on the remote endpoint.

The V-series regex already handles deepseek-v* IDs correctly. This fix addresses the ep-* pattern used by Volcengine ARK endpoints.

Fixes #17199 (Bug 1)

Fix

Add a regex pattern for ep-* prefixed IDs (Volcengine ARK endpoint IDs). When the model name matches this pattern AND DEEPSEEK_BASE_URL is set to a non-DeepSeek endpoint, pass the model name through unchanged.

Before: ep-20250428-xxxxxdeepseek-chat (404 on Volcengine) After: ep-20250428-xxxxxep-20250428-xxxxx (passed through when custom base URL is set)

Changes

  • hermes_cli/model_normalize.py:
    • Add _DEEPSEEK_CUSTOM_ENDPOINT_RE regex pattern for ep-* IDs
    • Update _normalize_for_deepseek() to pass through custom endpoint IDs when DEEPSEEK_BASE_URL points to a non-DeepSeek endpoint

Tests

  • All existing normalization tests should still pass
  • The fix is conservative: only passes through when custom base URL is explicitly configured

Changed files

  • hermes_cli/model_normalize.py (modified, +17/-0)

Code Example

⚠️  Normalized model 'deepseek-v3-2-251201' to 'deepseek-chat' for deepseek.
 HTTP 404: The model or endpoint deepseek-chat does not exist
RAW_BUFFERClick to expand / collapse

Problem

When configuring Hermes to use a custom OpenAI-compatible endpoint (e.g. Volcengine ARK at `ark.cn-beijing.volces.com/api/v3`) via the `deepseek` provider, two bugs make the configuration non-functional:

Bug 1 — Aggressive model name normalization Any model name that isn't exactly `deepseek-chat` or `deepseek-reasoner` is silently mapped to `deepseek-chat`. Versioned Volcengine model IDs like `deepseek-v3-2-251201` or `deepseek-v3-20251201` are mangled, causing the remote endpoint to return 404 since it doesn't host a model named `deepseek-chat`.

⚠️  Normalized model 'deepseek-v3-2-251201' to 'deepseek-chat' for deepseek.
→ HTTP 404: The model or endpoint deepseek-chat does not exist

Bug 2 — Credential pool base_url rebuilt from hardcoded defaults on startup The credential pool entry for `deepseek` is rebuilt from env vars on each startup using the hardcoded `api.deepseek.com/v1` URL, ignoring:

  • `model.base_url` set in `config.yaml`
  • Manual edits to `auth.json`

Only `DEEPSEEK_BASE_URL` env var is respected, but this isn't documented as the canonical way to set a custom endpoint.

Combined effect: The Volcengine API key (set in `DEEPSEEK_API_KEY`) gets sent to `api.deepseek.com` with model name `deepseek-chat`, producing 401/404 errors that appear unrelated to the real configuration mistake.

Root Cause Analysis

Bug 1: `_normalize_for_deepseek()` in `hermes_cli/model_normalize.py` has a fixed allowlist of only two model names. It has no mechanism to pass through versioned IDs or custom endpoint model names. The warning is emitted but cannot be suppressed.

Bug 2: The `_resolve_provider_credentials_from_entry()` path in `runtime_provider.py` checks `pool_url_is_default` to decide whether to apply `model.base_url` from config. However, the pool entry's `base_url` is already hardcoded to the DeepSeek default when built from `DEEPSEEK_API_KEY` env var, so `pool_url_is_default = True` and `config.yaml` base_url IS applied — but only if the pool entry hasn't been marked `exhausted` from a prior failed attempt. Once exhausted, the entry is skipped entirely and `config.yaml` base_url is never consulted.

TDD Fix Plan

  1. RED: Write a test that `_normalize_for_deepseek("deepseek-v3-2-251201")` returns `"deepseek-v3-2-251201"` unchanged when `DEEPSEEK_BASE_URL` is set to a non-DeepSeek endpoint. GREEN: In `_normalize_for_deepseek()`, before the keyword checks, detect versioned/dated model IDs (regex: `deepseek-v\d+` or `ep-\w+`) and pass them through when a custom `DEEPSEEK_BASE_URL` is configured.

  2. RED: Write a test that `_normalize_for_deepseek("deepseek-v3-20251201")` returns the name unchanged when `DEEPSEEK_BASE_URL` env var is set. GREEN: Covered by the same regex from step 1.

  3. RED: Write a test that an exhausted pool entry for `deepseek` does not prevent `model.base_url` from `config.yaml` being applied to the resolved base URL. GREEN: In `resolve_runtime_provider()`, ensure `model.base_url` from config is applied as a post-resolution override even when the pool entry is exhausted or skipped.

  4. RED: Write a test that setting `DEEPSEEK_BASE_URL` via `config set-env` results in the credential pool entry using that URL on next startup (i.e., pool rebuild respects the env var). GREEN: Document and enforce `DEEPSEEK_BASE_URL` as the canonical way to override the deepseek provider endpoint; surface it in `hermes setup` / `hermes model`.

REFACTOR: Add a note in `_normalize_for_deepseek()` docstring that custom endpoints should set `DEEPSEEK_BASE_URL`. Consider a general `--pass-model-name` flag or `model.normalize: false` config option to disable normalization globally.

Acceptance Criteria

  • `deepseek-v3-2-251201` (and other versioned IDs) are passed through unchanged when `DEEPSEEK_BASE_URL` points to a non-DeepSeek endpoint
  • An exhausted credential pool entry does not silently swallow `model.base_url` overrides from `config.yaml`
  • `hermes setup` or `hermes model` prompts for / documents `DEEPSEEK_BASE_URL` when the user selects deepseek provider
  • All existing normalization tests still pass
  • New tests cover the above behaviors

extent analysis

TL;DR

To fix the issue, update the _normalize_for_deepseek() function to pass through versioned model IDs when a custom DEEPSEEK_BASE_URL is configured, and modify the resolve_runtime_provider() function to apply model.base_url from config.yaml even when the pool entry is exhausted.

Guidance

  • Identify and update the _normalize_for_deepseek() function in hermes_cli/model_normalize.py to include a regex check for versioned model IDs and pass them through unchanged when DEEPSEEK_BASE_URL is set to a non-DeepSeek endpoint.
  • Modify the resolve_runtime_provider() function in runtime_provider.py to apply model.base_url from config.yaml as a post-resolution override, even when the pool entry is exhausted or skipped.
  • Document and enforce DEEPSEEK_BASE_URL as the canonical way to override the deepseek provider endpoint, and surface it in hermes setup / hermes model.
  • Consider adding a note in the _normalize_for_deepseek() docstring to indicate that custom endpoints should set DEEPSEEK_BASE_URL.

Example

import re

def _normalize_for_deepseek(model_name):
    # Check if DEEPSEEK_BASE_URL is set to a non-DeepSeek endpoint
    if 'DEEPSEEK_BASE_URL' in os.environ and not os.environ['DEEPSEEK_BASE_URL'].startswith('https://api.deepseek.com'):
        # Pass through versioned model IDs
        if re.match(r'deepseek-v\d+', model_name) or re.match(r'ep-\w+', model_name):
            return model_name
    # ... existing logic ...

Notes

The provided solution assumes that the DEEPSEEK_BASE_URL environment variable is set to a non-DeepSeek endpoint. Additional testing and verification may be necessary to ensure the fix

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING