hermes-hub/tests/test_plugin_passthrough.py
Hermes Team 2d62d3973e fix(plugin): отказ роутера больше не подменяет ответ модели в Hermes
Hub подключён к Hermes как middleware llm_execution и срабатывает на
каждом обращении к модели. Но Hermes роль не передаёт: в kwargs есть
model, provider, session_id, task_id — role нет. resolve_role поэтому
сваливается в роль по умолчанию, и КАЖДЫЙ вызов Hermes маршрутизируется
как orchestrator.

Цепочка orchestrator у владельца исчерпана целиком:
  ag-orch-fallback  skipped_unhealthy
  codex-orch        429 «account is not active, check billing»
  opengo-3          No API key found
  ag-w1/ag-w3       agy authentication failed or timed out

Роутер возвращал «⚠️ Hermes Router Failover Exhausted» как ответ
ассистента, и Hermes показывал это вместо ответа модели, хотя его
собственный провайдер работал. Это и есть «основной оркестратор не
выбрался» из отчёта владельца.

Теперь при router_error вызов уходит дальше по цепочке (next_call),
а отказ пишется в журнал уровнем warning с полным следом. Плагин обязан
быть незаметным при отказе: он может улучшить маршрутизацию, но не имеет
права сделать Hermes хуже, чем без него.

Проверено исполнением: Hermes получает ответ провайдера, а не текст
ошибки. Тесты: 287 passed (падает только известный нестабильный Tk-тест,
воспроизводится на чистом main).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:25:02 +07:00

70 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Плагин не должен делать Hermes хуже, чем без него.
Hermes вызывает Hub как middleware `llm_execution` на каждом обращении к
модели, но **роль не передаёт** — в kwargs есть model, provider, session_id,
task_id, а `role` нет. Роутер поэтому сваливается в роль по умолчанию
(`orchestrator`), и если её цепочка исчерпана, раньше он возвращал текст
«⚠️ Hermes Router Failover Exhausted» как ответ ассистента. Пользователь
видел это вместо ответа модели, хотя собственный провайдер Hermes работал.
"""
from __future__ import annotations
from typing import Any, Dict
import pytest
from antigravity_provider import hermes_plugin
class _ExhaustedEngine:
class config:
enabled = True
def route_request(self, request: Dict[str, Any], role: Any = None, session_id: Any = None) -> Dict[str, Any]:
return {
"id": "router-fail-1",
"model": "router-failover",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "⚠️ Hermes Router Failover Exhausted"},
"finish_reason": "error",
}
],
"router_error": True,
"failover_trail": [{"profile_id": "codex-orch", "status": "failed"}],
}
@pytest.fixture
def exhausted_router(monkeypatch):
monkeypatch.setattr(hermes_plugin, "get_router_engine", lambda: _ExhaustedEngine(), raising=False)
import antigravity_provider.router as router_pkg
monkeypatch.setattr(router_pkg, "get_router_engine", lambda: _ExhaustedEngine())
def test_exhausted_failover_passes_call_downstream(exhausted_router):
downstream_calls = []
def next_call(payload=None):
downstream_calls.append(payload)
return {
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "настоящий ответ"}, "finish_reason": "stop"}
]
}
result = hermes_plugin.antigravity_llm_execution(
request={"messages": [{"role": "user", "content": "ping"}]},
next_call=next_call,
provider="gemini",
model="gemini-3.7-flash",
session_id="s1",
)
assert len(downstream_calls) == 1, "отказ роутера обязан уходить вниз по цепочке, а не подменять ответ"
content = result["choices"][0]["message"]["content"]
assert content == "настоящий ответ"
assert "Failover Exhausted" not in content