Skip to content

feat(agent): add model.preserve_dots opt-in override for Anthropic-compatible proxies#13890

Open
Chao1208 wants to merge 1 commit into
NousResearch:mainfrom
Chao1208:feat/preserve-dots-override
Open

feat(agent): add model.preserve_dots opt-in override for Anthropic-compatible proxies#13890
Chao1208 wants to merge 1 commit into
NousResearch:mainfrom
Chao1208:feat/preserve-dots-override

Conversation

@Chao1208

Copy link
Copy Markdown

What

Adds an explicit model.preserve_dots: true config flag that forces
AIAgent._anthropic_preserve_dots() to return the configured value,
overriding the provider/base-URL allowlist. This lets users point
Hermes at third-party Anthropic-compatible proxies — OneAPI, LiteLLM,
FastGPT, company-internal gateways, etc. — whose registered model IDs
contain dots, without requiring Hermes to hardcode every new proxy URL.

Why

The default behaviour of normalize_model_name converts dots to
hyphens (claude-opus-4.6claude-opus-4-6) to match the
Anthropic/OpenRouter convention. _anthropic_preserve_dots opts
specific providers (alibaba, minimax, opencode-go, opencode-zen,
zai, bedrock) and base-URL substrings (dashscope, aliyuncs,
minimax, opencode.ai/zen/, bigmodel.cn, bedrock-runtime.) out
of this mangling.

That allowlist doesn't scale. Any corporate or third-party Anthropic-
compatible gateway that registers a dotted model ID silently breaks:

HTTP 503: no available channel for model "claude opus 4-6"

…even though the operator registered the channel as Claude Opus 4.6.
Reproducer: point Hermes at a OneAPI-style proxy with:

model:
  default: Claude Opus 4.6
  provider: custom
  base_url: https://<your-oneapi-proxy>
  api_mode: anthropic_messages
  api_key: sk-...

…and watch the request body go out as "model": "Claude Opus 4-6".
The proxy can't match that against its catalog and returns 503.

Rather than keep adding one-off URL substrings to the allowlist, this
PR exposes a single opt-in knob. Users who know their proxy needs
dotted model IDs can add one line to ~/.hermes/config.yaml:

model:
  preserve_dots: true

…and every future proxy in this class is handled without a Hermes
release.

Behaviour matrix

model.preserve_dots Effect
true Always preserve dots, regardless of provider / base_url
false Always mangle dots — escape hatch if the allowlist is wrong
unset / non-bool Fall back to existing allowlist (no behaviour change)

The override is read once in AIAgent.__init__ and cached on the
instance. Existing unit tests that construct lightweight agents via
SimpleNamespace don't set the attribute and therefore land on the
fallback path, i.e. this is fully backwards compatible.

How to test

Automated

pytest tests/agent/test_preserve_dots_override.py -v

Eight new cases covering:

  • preserve_dots: true on a URL not in the allowlist (core case)
  • preserve_dots: true overriding provider=anthropic
  • preserve_dots: false overriding provider=bedrock
  • preserve_dots: false overriding dashscope.aliyuncs.com URL
  • missing / None / non-bool attribute values → fall back to allowlist

Related regression suites — tests/agent/test_bedrock_integration.py,
tests/agent/test_anthropic_adapter.py,
tests/agent/test_minimax_provider.py — all still pass (208/208).

Manual

Against a real OneAPI-style proxy that registers Claude Opus 4.6:

  1. Configure ~/.hermes/config.yaml:

    model:
      default: Claude Opus 4.6
      provider: custom
      base_url: https://<proxy>
      api_mode: anthropic_messages
      api_key: sk-<your-key>
      preserve_dots: true
  2. hermes chat -q "say hi in exactly 3 words" → returns a normal
    assistant message. Without preserve_dots: true the proxy
    responds with HTTP 503 no available channel for claude opus 4-6.

  3. Inspect ~/.hermes/sessions/request_dump_*.json and confirm
    request.body.model is the dotted "Claude Opus 4.6".

Platforms tested

  • macOS 14.2.1 (darwin 23.2.0, aarch64), Python 3.11.15

No platform-specific code paths are touched — the change is pure
Python config reading.

Risks

Minimal:

  • Fallback branch is unchanged (users who don't set the flag see
    identical behaviour).
  • Non-bool values are defensively rejected in __init__, so
    hand-edited YAML like preserve_dots: "yes" can't silently
    flip the flag.
  • One extra dict.get() in the hot path of _anthropic_preserve_dots.

Related

Add an explicit ``model.preserve_dots: true`` config flag that forces
``_anthropic_preserve_dots`` to return the configured value regardless
of the provider/base_url allowlist. This lets users point Hermes at
third-party Anthropic-compatible proxies (OneAPI, LiteLLM, FastGPT,
company-internal gateways, etc.) whose registered model IDs contain
dots — e.g. ``Claude Opus 4.6`` routed to AWS Bedrock — without having
to hardcode every new proxy URL into Hermes itself.

Motivation

When pointing Hermes at a corporate OneAPI gateway configured with
``model: Claude Opus 4.6`` and ``api_mode: anthropic_messages``, the
outgoing request body contained ``"model": "Claude Opus 4-6"`` because
``normalize_model_name`` strips dots by default (OpenRouter convention).
The gateway responds with HTTP 503 "no available channel for model
claude opus 4-6" because its catalog only knows the dotted form.

The existing allowlist in ``_anthropic_preserve_dots`` handles
Alibaba/DashScope, MiniMax, OpenCode, ZAI, Bedrock — but every
third-party Anthropic-compatible proxy would need its own entry,
which doesn't scale.

Behavior

- ``preserve_dots: true``  → always preserve dots
- ``preserve_dots: false`` → always mangle dots (escape hatch)
- missing / non-bool       → fall back to existing allowlist (no
  behavior change for users who don't opt in)

The override is read once in ``__init__`` and cached on the instance,
so existing unit tests that construct ``AIAgent`` via ``SimpleNamespace``
are unaffected.

Test plan

- New test file ``tests/agent/test_preserve_dots_override.py`` with 8
  cases covering True/False/missing/None/non-bool and interactions
  with the existing allowlist.
- Existing bedrock/anthropic/minimax preserve-dots tests still pass
  (208/208, no regressions).
- End-to-end verified against a real OneAPI gateway with
  ``model.default: Claude Opus 4.6`` — request body now carries the
  dotted model ID and the gateway routes successfully.

Tested on: macOS 14 (darwin 23.2.0), Python 3.11.15.

Made-with: Cursor
@Chao1208

Copy link
Copy Markdown
Author

Friendly ping @teknium1 — opened this 6 days ago. The change is small and additive (an opt-in model.preserve_dots flag, default behavior unchanged), with a dedicated unit test in tests/agent/test_preserve_dots_override.py. CI is currently in action_required waiting for a maintainer to authorize the workflows. Happy to address any feedback. Thanks for your time!

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

Labels

area/config Config system, migrations, profiles comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants