Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions agent/model_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,34 @@ def get_model_context_length(
if config_context_length is not None and isinstance(config_context_length, int) and config_context_length > 0:
return config_context_length

# 0a. MoA virtual provider — ``model`` is a preset name, not a real model,
# and ``base_url`` is the local virtual endpoint, so every probe below would
# miss and fall through to the 256K default. The aggregator is the acting
# model, so resolve the context window from the aggregator slot's real
# provider+model instead. References are advisory-only and never bound the
# acting context, so they're ignored here.
if (provider or "").strip().lower() == "moa":
try:
from hermes_cli.config import load_config
from hermes_cli.moa_config import resolve_moa_preset
from hermes_cli.runtime_provider import resolve_runtime_provider

preset = resolve_moa_preset(load_config().get("moa") or {}, model)
agg = preset.get("aggregator") or {}
agg_provider = str(agg.get("provider") or "").strip()
agg_model = str(agg.get("model") or "").strip()
if agg_model and agg_provider and agg_provider.lower() != "moa":
rt = resolve_runtime_provider(requested=agg_provider, target_model=agg_model)
return get_model_context_length(
agg_model,
base_url=rt.get("base_url", "") or "",
api_key=rt.get("api_key", "") or "",
provider=agg_provider,
)
except Exception:
logger.debug("MoA aggregator context-length resolution failed", exc_info=True)
# Fall through to the generic default if aggregator resolution failed.

# 0b. custom_providers per-model override — check before any probe.
# This closes the gap where /model switch and display paths used to fall
# back to 128K despite the user having a per-model context_length set.
Expand Down
48 changes: 48 additions & 0 deletions tests/agent/test_model_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,3 +1524,51 @@ def test_grok_4_not_clobbered(self, tmp_path, monkeypatch):
slug, base_url=base, api_key="", provider="xai"
)
assert ctx == 256_000, f"{slug} should stay 256000, got {ctx}"


class TestMoAContextLength:
"""MoA virtual provider resolves context from the aggregator slot, not 256K default."""

def _write_moa_config(self, home, aggregator):
import os
os.makedirs(home, exist_ok=True)
with open(os.path.join(home, "config.yaml"), "w") as f:
yaml.safe_dump(
{
"moa": {
"default_preset": "p",
"presets": {
"p": {
"enabled": True,
"reference_models": [
{"provider": "openrouter", "model": "openai/gpt-5.5"}
],
"aggregator": aggregator,
}
},
}
},
f,
)

def test_moa_resolves_from_aggregator(self, tmp_path, monkeypatch):
home = str(tmp_path / ".hermes")
monkeypatch.setenv("HERMES_HOME", home)
self._write_moa_config(home, {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"})

# The MoA preset name + virtual base_url would otherwise fall through to
# the 256K default; instead it mirrors the aggregator's real window.
agg_ctx = get_model_context_length(
"anthropic/claude-opus-4.8", base_url="https://openrouter.ai/api/v1", provider="openrouter"
)
moa_ctx = get_model_context_length("p", base_url="http://127.0.0.1/v1", provider="moa")
assert moa_ctx == agg_ctx

def test_moa_config_override_still_wins(self, tmp_path, monkeypatch):
home = str(tmp_path / ".hermes")
monkeypatch.setenv("HERMES_HOME", home)
self._write_moa_config(home, {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"})
ctx = get_model_context_length(
"p", base_url="http://127.0.0.1/v1", provider="moa", config_context_length=500_000
)
assert ctx == 500_000
Loading