Files
hermes-agent/plugins/model-providers/deepinfra/__init__.py
teknium1 b027a4658e fix: trim DeepInfra reasoning salvage to the invariant and document it
Follow-up to the cherry-picked #111876 (@KoNit-K), which shares the design
of the earlier #111875 by the issue author (@ats3v): emit DeepInfra's
top-level ``reasoning_effort`` from the provider profile, ungated on
``supports_reasoning``, ``none`` as the only off switch, ``xhigh`` native,
``ultra`` clamped to ``max`` via the shared vocabulary, unset/unknown omitted.

- drop the constructor/blank-line reformat churn (byte-identical to main)
- replace the 14-case test file with two invariant tests: the profile's
  config -> top-level field table, and the transport main-turn path with
  ``supports_reasoning=False`` (the gate the core allowlist actually passes)
- docs: DeepInfra subsection in integrations/providers.md describing the
  two-directional reasoning control

Offline kwargs probe: before every reasoning_config -> ({}, {}) and the
main turn carried no reasoning field; after ``high`` -> ``reasoning_effort:
high``, ``{'enabled': False}`` -> ``none``, ``ultra`` -> ``max``, unset and
unknown levels omitted, aux calls stop emitting the generic
``extra_body.reasoning`` for this provider.

Co-authored-by: Georgi Atsev <georgi@deepinfra.com>
2026-09-15 18:22:40 -07:00

68 lines
3.3 KiB
Python

"""DeepInfra provider profile (chat surface; image-gen/TTS/STT are wired via
their own plugin subsystems)."""
from typing import Any
from agent.reasoning_effort import OPENAI_COMPAT_WIRE_EFFORTS, clamp_effort, requested_effort
from providers import register_provider
from providers.base import ProviderProfile
class _DeepInfraProfile(ProviderProfile):
"""DeepInfra profile with live vision-default discovery, so shared vision
resolution in ``agent/auxiliary_client.py`` stays provider-agnostic."""
def build_api_kwargs_extras(
self, *, reasoning_config: dict | None = None, **context: Any
) -> tuple[dict[str, Any], dict[str, Any]]:
"""Map Hermes reasoning controls to DeepInfra's top-level ``reasoning_effort``.
DeepInfra applies a per-model default when the field is absent (DeepSeek-V4.x off,
GLM/Qwen-Thinking on), so ``none`` is the only working off switch and an unset effort
is omitted rather than guessed. The core ``_supports_reasoning_extra_body`` allowlist
does not know this host, so the transport always passes ``supports_reasoning=False``
here — gating on it would make the method a permanent no-op (#111872).
"""
if isinstance(reasoning_config, dict) and reasoning_config.get("enabled") is False:
return {}, {"reasoning_effort": "none"}
effort = requested_effort(reasoning_config)
clamped = clamp_effort(effort, OPENAI_COMPAT_WIRE_EFFORTS)
return ({}, {"reasoning_effort": clamped}) if clamped in OPENAI_COMPAT_WIRE_EFFORTS else ({}, {})
def default_vision_model(self): # type: ignore[override]
"""First vision-capable *chat* model from the live catalog, or None. Key-gated so a box
without DEEPINFRA_API_KEY never pays the round-trip; requires the ``chat`` surface tag so
an image-gen model carrying a ``vision`` tag can't be picked as a chat vision backend."""
from agent.secret_scope import get_secret
if not (get_secret("DEEPINFRA_API_KEY") or "").strip():
return None
try:
from hermes_cli.models import _fetch_deepinfra_models_by_tag
items = _fetch_deepinfra_models_by_tag("chat")
except Exception:
return None
for item in items or []:
metadata = item.get("metadata") or {}
tags = metadata.get("tags") if isinstance(metadata, dict) else None
if isinstance(tags, list) and "vision" in tags and item.get("id"):
return item["id"]
return None
deepinfra = _DeepInfraProfile(
name="deepinfra", aliases=("deep-infra", "deepinfra-ai"), display_name="DeepInfra",
description="DeepInfra — 100+ open models, pay-per-use", signup_url="https://deepinfra.com/dash/api_keys",
env_vars=("DEEPINFRA_API_KEY", "DEEPINFRA_BASE_URL"), base_url="https://api.deepinfra.com/v1/openai",
auth_type="api_key",
default_max_tokens=None, # DeepInfra applies its documented per-model limit
# The only hardcoded DeepInfra model: aux resolution is synchronous, so it
# can't wait on a catalog round-trip. Everything else is discovered live.
default_aux_model="deepseek-ai/DeepSeek-V4-Flash",
# Empty on purpose: the live catalog is the source of truth; an empty picker
# beats silently routing to a retired model.
fallback_models=(),
)
register_provider(deepinfra)