mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
Core branched on the Copilot slug/host for four wire decisions. Replace them with generic ProviderProfile hooks (providers/base.py L1 block) and move the Copilot logic into plugins/model-providers/copilot/wire.py: - api_mode_for_model(model, *, api_key=None): run_agent Responses routing, aux CodexAuxiliaryClient wrap, model_switch.model_derived_api_mode (copilot rows of _PROVIDER_API_MODE_OVERRIDES removed). - client_headers(*, base_url, api_key, has_images): agent_init/_client_lifecycle host tables, the client-kwargs chokepoint guard (_ensure_route_client_headers), the image request-client header swap, both aux header branches. - request_headers(*, base_url, model, user_initiated): first-call-of-a-user-turn attribution in turn_api_request and MoA reference slots. - max_tokens_param(*, base_url, model): run_agent._max_tokens_param and auxiliary_max_tokens_param. agent/provider_route_hooks.py asks the named provider's profile first, then any registered profile overriding the hook, so custom routes at a provider's host keep its quirks. Plugin absent = generic behaviour.
63 lines
2.9 KiB
Python
63 lines
2.9 KiB
Python
"""Init-time fallback chain: entry normalization, chain setup, and the fallback's api_mode."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, Dict, List
|
|
|
|
logger = logging.getLogger("run_agent")
|
|
|
|
|
|
def _fallback_entries(fallback_model) -> list[dict[str, Any]]:
|
|
"""Normalize legacy single-dict ``fallback_model`` / list ``fallback_providers``."""
|
|
if isinstance(fallback_model, dict):
|
|
fallback_model = [fallback_model]
|
|
if not isinstance(fallback_model, list):
|
|
return []
|
|
return [
|
|
f for f in fallback_model if isinstance(f, dict) and f.get("provider") and f.get("model")
|
|
]
|
|
|
|
|
|
def _init_fallback_chain(agent, fallback_model):
|
|
# Stable pool-entry identity: OAuth refreshes can replace the token before a failed
|
|
# request is recovered, so the key value alone can't attribute the failure.
|
|
from agent.agent_runtime_helpers import sync_credential_pool_entry_id
|
|
sync_credential_pool_entry_id(agent)
|
|
|
|
# Ordered backups tried when the primary is exhausted (legacy single-dict or list).
|
|
agent._fallback_chain = _fallback_entries(fallback_model)
|
|
agent._fallback_index = 0
|
|
agent._fallback_activated = getattr(agent, "_fallback_activated", False)
|
|
# Legacy attribute kept for backward compat (tests, external callers)
|
|
agent._fallback_model = agent._fallback_chain[0] if agent._fallback_chain else None
|
|
chain = agent._fallback_chain
|
|
if chain and not agent.quiet_mode:
|
|
labels = [f"{f['model']} ({f['provider']})" for f in chain]
|
|
if len(chain) == 1:
|
|
print(f"🔄 Fallback model: {labels[0]}")
|
|
else:
|
|
print(f"🔄 Fallback chain ({len(chain)} providers): " + " → ".join(labels))
|
|
|
|
|
|
def recompute_init_fallback_api_mode(agent, fb_client) -> None:
|
|
"""Give an init-time fallback its own api_mode instead of the unreachable primary's.
|
|
|
|
A Nous primary configured with ``api_mode: codex_responses`` otherwise leaves a fallback whose
|
|
profile serves that model on chat completions (``api_mode_for_model``) on the Responses API,
|
|
which silently drops reasoning content (#46527).
|
|
The per-turn path (``try_activate_fallback``) already recomputes via
|
|
``_fallback_api_mode_resolved``; this keeps init in agreement, and runs before the
|
|
``_primary_runtime`` snapshot so ``restore_primary_runtime`` replays the recomputed mode.
|
|
A detection failure keeps the primary's mode and never blocks the fallback itself.
|
|
"""
|
|
from agent.chat_completion_helpers import _fallback_api_mode_resolved
|
|
try:
|
|
agent.api_mode = _fallback_api_mode_resolved(
|
|
agent, agent.provider, agent.model, str(getattr(fb_client, "base_url", "") or ""))
|
|
except Exception:
|
|
logger.debug("Init-time fallback api_mode detection failed for %s", agent.provider, exc_info=True)
|
|
return
|
|
if hasattr(agent, "_transport_cache"):
|
|
agent._transport_cache.clear()
|