From 4fe3d288eb73f320dc3a43c6ed72608780cebf5b Mon Sep 17 00:00:00 2001 From: KoNit-K <124019182+KoNit-K@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:24:15 +0800 Subject: [PATCH] fix(providers): send DeepInfra reasoning effort --- plugins/model-providers/deepinfra/__init__.py | 41 +++++++++- .../model_providers/test_deepinfra_profile.py | 74 +++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 tests/plugins/model_providers/test_deepinfra_profile.py diff --git a/plugins/model-providers/deepinfra/__init__.py b/plugins/model-providers/deepinfra/__init__.py index 313683e94f..2ba8fee8dd 100644 --- a/plugins/model-providers/deepinfra/__init__.py +++ b/plugins/model-providers/deepinfra/__init__.py @@ -1,6 +1,13 @@ """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 @@ -9,6 +16,29 @@ 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 wire field. + + DeepInfra applies a per-model default when the field is absent, while + ``none`` is its explicit off switch. This route does not advertise a + reasoning capability to the shared transport, so it must not be gated + on ``supports_reasoning``. + """ + 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 @@ -19,6 +49,7 @@ class _DeepInfraProfile(ProviderProfile): return None try: from hermes_cli.models import _fetch_deepinfra_models_by_tag + items = _fetch_deepinfra_models_by_tag("chat") except Exception: return None @@ -31,9 +62,13 @@ class _DeepInfraProfile(ProviderProfile): 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", + 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 diff --git a/tests/plugins/model_providers/test_deepinfra_profile.py b/tests/plugins/model_providers/test_deepinfra_profile.py new file mode 100644 index 0000000000..f76c1fbb0d --- /dev/null +++ b/tests/plugins/model_providers/test_deepinfra_profile.py @@ -0,0 +1,74 @@ +"""Regression coverage for DeepInfra's top-level reasoning_effort wire field.""" + +from __future__ import annotations + +import pytest + + +@pytest.fixture +def deepinfra_profile(): + """Resolve the registered profile through the normal plugin discovery path.""" + import model_tools # noqa: F401 + import providers + + profile = providers.get_provider_profile("deepinfra") + assert profile is not None, "deepinfra provider profile must be registered" + return profile + + +class TestDeepInfraReasoningEffort: + def test_default_preserves_the_model_default(self, deepinfra_profile): + assert deepinfra_profile.build_api_kwargs_extras(reasoning_config=None) == ( + {}, + {}, + ) + + @pytest.mark.parametrize( + "effort", ("minimal", "low", "medium", "high", "xhigh", "max") + ) + def test_explicit_efforts_are_sent_verbatim(self, deepinfra_profile, effort): + assert deepinfra_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": effort} + ) == ({}, {"reasoning_effort": effort}) + + def test_ultra_clamps_to_deepinfra_maximum(self, deepinfra_profile): + assert deepinfra_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "ultra"} + ) == ({}, {"reasoning_effort": "max"}) + + @pytest.mark.parametrize( + "reasoning_config", ({"enabled": False}, {"enabled": False, "effort": "high"}) + ) + def test_disabled_sends_the_explicit_off_value( + self, deepinfra_profile, reasoning_config + ): + assert deepinfra_profile.build_api_kwargs_extras( + reasoning_config=reasoning_config + ) == ({}, {"reasoning_effort": "none"}) + + @pytest.mark.parametrize( + "reasoning_config", + ({}, {"enabled": True}, {"enabled": True, "effort": "future-tier"}), + ) + def test_missing_or_unknown_effort_preserves_the_model_default( + self, deepinfra_profile, reasoning_config + ): + assert deepinfra_profile.build_api_kwargs_extras( + reasoning_config=reasoning_config + ) == ({}, {}) + + def test_transport_includes_top_level_reasoning_effort_without_capability_gate( + self, deepinfra_profile + ): + from agent.transports.chat_completions import ChatCompletionsTransport + + kwargs = ChatCompletionsTransport().build_kwargs( + model="deepseek-ai/DeepSeek-V4.1-Flash", + messages=[{"role": "user", "content": "ping"}], + tools=None, + provider_profile=deepinfra_profile, + provider_name="deepinfra", + reasoning_config={"enabled": True, "effort": "high"}, + supports_reasoning=False, + ) + assert kwargs["reasoning_effort"] == "high"