mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(moa): name the billed aggregator in the gateway /model block and qualify the advise-once copy
Review follow-up for #112359: - gateway `_format_session_info` (the /model info block) printed only `Model: <preset> / Provider: moa`, so the messaging surfaces still hid who pays; append the same "Acting model (billed for the run)" line the CLI switch summary prints. - `_provider_mismatch_notice` compared providers case-sensitively and treated the routing pseudo-provider `auto` as a billing seat, printing a misleading "not to OpenAI" / "not to auto" line; lower-case both sides and skip `auto`. - "references only advise once per user turn" is only true for the default `fanout: user_turn`; say "by default" on the inventory row, the `hermes moa list` header, the Desktop pill/description (en/zh/zh-hant) and the docs line.
This commit is contained in:
1 parent
587c7d9631
commit
c8f0f6b136
8 files changed
+31
-12
No files matched your search
@@ -1386,10 +1386,10 @@ export const en: Translations = {
|
||||
moaTitle: 'Mixture of Agents',
|
||||
moaPreset: 'Preset',
|
||||
moaDescription:
|
||||
'Configure named presets that appear as models under the Mixture of Agents provider. The aggregator is the acting model — it runs every step of the tool loop, and almost all of the run’s cost is billed to its provider. References only advise once per user turn.',
|
||||
'Configure named presets that appear as models under the Mixture of Agents provider. The aggregator is the acting model — it runs every step of the tool loop, and almost all of the run’s cost is billed to its provider. References only advise once per user turn by default.',
|
||||
moaAggregator: 'Aggregator',
|
||||
moaAggregatorBilled: 'acting model · billed for the run',
|
||||
moaReferenceHint: 'advises once per turn',
|
||||
moaReferenceHint: 'advises once per turn by default',
|
||||
tasks: {
|
||||
vision: { label: 'Vision', hint: 'Image analysis' },
|
||||
compression: { label: 'Compression', hint: 'Context compaction' },
|
||||
|
||||
@@ -1220,10 +1220,10 @@ export const zhHant = defineLocale({
|
||||
moaTitle: '混合代理(Mixture of Agents)',
|
||||
moaPreset: '預設',
|
||||
moaDescription:
|
||||
'設定以「混合代理」提供者下模型形式出現的命名預設。聚合模型是執行模型——它執行工具迴圈的每一步,整個運行幾乎全部費用都計入其提供者。參考模型每輪使用者訊息僅提供一次建議。',
|
||||
'設定以「混合代理」提供者下模型形式出現的命名預設。聚合模型是執行模型——它執行工具迴圈的每一步,整個運行幾乎全部費用都計入其提供者。參考模型預設每輪使用者訊息僅提供一次建議。',
|
||||
moaAggregator: '聚合模型',
|
||||
moaAggregatorBilled: '執行模型 · 整個運行在此計費',
|
||||
moaReferenceHint: '每輪僅建議一次',
|
||||
moaReferenceHint: '默認每輪僅建議一次',
|
||||
tasks: {
|
||||
vision: { label: '視覺', hint: '圖片分析' },
|
||||
compression: { label: '壓縮', hint: '上下文壓縮' },
|
||||
|
||||
@@ -1581,10 +1581,10 @@ export const zh = defineLocale({
|
||||
moaTitle: '混合智能体(Mixture of Agents)',
|
||||
moaPreset: '预设',
|
||||
moaDescription:
|
||||
'配置以「混合智能体」提供商下模型形式出现的命名预设。聚合模型是执行模型——它运行工具循环的每一步,整个运行几乎全部费用都计入其提供商。参考模型每轮用户消息仅提供一次建议。',
|
||||
'配置以「混合智能体」提供商下模型形式出现的命名预设。聚合模型是执行模型——它运行工具循环的每一步,整个运行几乎全部费用都计入其提供商。参考模型默认每轮用户消息仅提供一次建议。',
|
||||
moaAggregator: '聚合模型',
|
||||
moaAggregatorBilled: '执行模型 · 整个运行在此计费',
|
||||
moaReferenceHint: '每轮仅建议一次',
|
||||
moaReferenceHint: '默认每轮仅建议一次',
|
||||
tasks: {
|
||||
vision: { label: '视觉', hint: '图片分析' },
|
||||
compression: { label: '压缩', hint: '上下文压缩' },
|
||||
|
||||
@@ -2233,6 +2233,13 @@ class GatewayTurnMixin:
|
||||
f"◆ Provider: {resolved.provider or 'openrouter'}",
|
||||
f"◆ Context: {ctx_display} tokens ({ctx_source})",
|
||||
]
|
||||
if (resolved.provider or "") == "moa":
|
||||
# The preset name hides who pays: the aggregator runs every tool-loop step (#112359).
|
||||
from hermes_cli.config import load_config
|
||||
from hermes_cli.moa_config import normalize_moa_config
|
||||
agg = normalize_moa_config(load_config().get("moa"))["presets"].get(resolved.model, {}).get("aggregator") or {}
|
||||
if agg:
|
||||
lines.append(f"◆ Acting model (billed for the run): {agg.get('provider')}:{agg.get('model')}")
|
||||
base_url = resolved.base_url
|
||||
if base_url and base_url_hostname(base_url) in ("localhost", "127.0.0.1", "0.0.0.0"):
|
||||
lines.append(f"◆ Endpoint: {base_url}")
|
||||
|
||||
@@ -744,6 +744,6 @@ def _moa_provider_row(current_provider: str = "") -> dict | None:
|
||||
return _row(
|
||||
"moa", "Mixture of Agents", (current_provider or "").lower() == "moa", models=models,
|
||||
total_models=len(models), source="virtual", authenticated=True, auth_type="virtual",
|
||||
warning="Aggregator is the acting model billed for the run; references only advise once per user turn.")
|
||||
warning="Aggregator is the acting model billed for the run; references only advise once per user turn by default.")
|
||||
except Exception:
|
||||
return None
|
||||
@@ -76,12 +76,12 @@ def _provider_mismatch_notice(
|
||||
if isinstance(cfg, dict):
|
||||
model_section = cfg.get("model")
|
||||
if isinstance(model_section, dict):
|
||||
main_provider = str(model_section.get("provider") or "").strip()
|
||||
agg_provider = str((aggregator or {}).get("provider") or "").strip()
|
||||
main_provider = str(model_section.get("provider") or "").strip().lower()
|
||||
agg_provider = str((aggregator or {}).get("provider") or "").strip().lower()
|
||||
if (
|
||||
not main_provider
|
||||
or not agg_provider
|
||||
or main_provider == "moa"
|
||||
or main_provider in ("moa", "auto") # "auto" is a routing pseudo-provider, not a billing seat
|
||||
or main_provider == agg_provider
|
||||
):
|
||||
return None
|
||||
@@ -98,7 +98,7 @@ def _print_config(config: dict[str, Any]) -> None:
|
||||
print(f"Active in config: {cfg.get('active_preset') or '(off)'}")
|
||||
for name, preset in cfg["presets"].items():
|
||||
print(f"\n{'*' if name == cfg['default_preset'] else ' '} {name}")
|
||||
print(" Reference models (advise once per user turn):")
|
||||
print(" Reference models (advise once per user turn by default):")
|
||||
for idx, slot in enumerate(preset["reference_models"], start=1):
|
||||
print(f" {idx}. {_format_slot(slot)}")
|
||||
agg_slot = preset["aggregator"]
|
||||
|
||||
@@ -64,6 +64,18 @@ class TestFormatSessionInfo:
|
||||
assert "localhost:11434" in info
|
||||
assert "8K" in info
|
||||
|
||||
def test_moa_preset_names_the_billed_aggregator(self, runner, tmp_path):
|
||||
"""#112359: the preset name hides who pays; /model must name the acting aggregator."""
|
||||
p1, p2, p3 = _patch_info(tmp_path, "model:\n default: review\n provider: moa\n",
|
||||
"review", {"provider": "moa", "base_url": "", "api_key": ""})
|
||||
moa_cfg = {"moa": {"presets": {"review": {
|
||||
"reference_models": [{"provider": "openai", "model": "gpt-5.5"}],
|
||||
"aggregator": {"provider": "nous", "model": "claude-opus-4.8"},
|
||||
}}}}
|
||||
with p1, p2, p3, patch("hermes_cli.config.load_config", return_value=moa_cfg):
|
||||
info = runner._format_session_info()
|
||||
assert "Acting model (billed for the run): nous:claude-opus-4.8" in info
|
||||
|
||||
def test_named_custom_provider_keeps_context_pin_without_model_base_url(
|
||||
self, runner, tmp_path
|
||||
):
|
||||
|
||||
@@ -218,7 +218,7 @@ hermes moa configure review # create or update a named preset
|
||||
hermes moa delete review
|
||||
```
|
||||
|
||||
`hermes moa list` marks the aggregator as the acting model that carries almost all of the cost and lists references as advising once per user turn. When the aggregator's provider differs from your main `model.provider`, both `list` and `configure` add:
|
||||
`hermes moa list` marks the aggregator as the acting model that carries almost all of the cost and lists references as advising once per user turn (by default). When the aggregator's provider differs from your main `model.provider`, both `list` and `configure` add:
|
||||
|
||||
```text
|
||||
Aggregator is on nous; the whole tool loop will be billed there, not to openai-codex.
|
||||
|
||||
Reference in new issue
Block a user