hermes-agent/tests/plugins/model_providers/test_custom_profile.py
Epoxidex 8662254ab2 fix(ollama): emit top-level reasoning_effort=none on /v1/chat/completions (#25758)
Ollama's /v1/chat/completions silently ignores extra_body.think (it only
honours it on /api/chat — ollama/ollama#14820), so agent.reasoning_effort:
none never actually disabled thinking on OpenAI-compatible Ollama routes.
Emit the top-level reasoning_effort='none' field (which Ollama respects)
alongside think=False (kept for proxies and the native /api/chat path).

The PR's second half (propagating reasoning_config to the background-review
fork) already landed on main via agent/background_review.py, so only the
provider-profile change is salvaged here, resolved onto the current
GLM/effort-aware profile.

Salvaged from PR #29820 by @Epoxidex.
2026-07-15 06:38:28 -07:00

124 lines
5.1 KiB
Python

"""Unit tests for the custom provider profile's reasoning wiring.
``provider=custom`` covers any OpenAI-compatible endpoint the user points
Hermes at — local Ollama, vLLM, llama.cpp, and hosted reasoning APIs like
GLM-5.2 on Volcengine ARK. Before #57601's salvage, ``CustomProfile`` emitted
nothing when reasoning was *enabled*, so a configured ``reasoning_effort``
was silently dropped for every custom endpoint.
These tests pin the wire-shape contract:
- disabled → extra_body.think = False
- enabled + effort → top-level reasoning_effort (native OpenAI-compat
format GLM/ARK expect), passed through verbatim
including ``max``/``xhigh``
- enabled + no effort → nothing emitted (endpoint's server default applies)
- ollama_num_ctx → extra_body.options.num_ctx, orthogonal to reasoning
"""
from __future__ import annotations
import pytest
@pytest.fixture
def custom_profile():
"""Resolve the registered custom profile via the global registry.
Importing ``model_tools`` triggers plugin discovery, which registers the
``custom`` profile. Going through ``get_provider_profile`` keeps the test
honest — if the registered class is ever downgraded to a plain
``ProviderProfile``, the assertions below collapse.
"""
import model_tools # noqa: F401
import providers
profile = providers.get_provider_profile("custom")
assert profile is not None, "custom provider profile must be registered"
return profile
class TestCustomReasoningWireShape:
"""``build_api_kwargs_extras`` produces the correct wire format."""
def test_no_reasoning_config_emits_nothing(self, custom_profile):
"""Unset reasoning → omit everything so the endpoint's default applies."""
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config=None, model="glm-5.2"
)
assert eb == {}
assert tl == {}
def test_disabled_sends_think_false(self, custom_profile):
"""enabled=False → reasoning_effort='none' top-level + think=False.
Both fields are required: Ollama's /v1/chat/completions silently
ignores extra_body.think (only /api/chat honours it — ollama#14820)
but respects top-level reasoning_effort (#25758). think=False stays
for proxies and the native /api/chat path.
"""
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": False}, model="glm-5.2"
)
assert eb == {"think": False}
assert tl == {"reasoning_effort": "none"}
def test_effort_none_sends_think_false(self, custom_profile):
"""effort='none' is the disable alias → same dual emission."""
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": "none"}, model="glm-5.2"
)
assert eb == {"think": False}
assert tl == {"reasoning_effort": "none"}
@pytest.mark.parametrize(
"effort", ["minimal", "low", "medium", "high", "xhigh", "max"]
)
def test_enabled_effort_goes_top_level(self, custom_profile, effort):
"""enabled + effort → TOP-LEVEL reasoning_effort, passed through verbatim.
GLM-5.2/ARK and OpenAI-compatible reasoning APIs read reasoning_effort
as a top-level string, not nested in extra_body. ``max`` is GLM's
native deep-reasoning level and must survive.
"""
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": effort}, model="glm-5.2"
)
assert tl == {"reasoning_effort": effort}
assert "reasoning_effort" not in eb
assert "think" not in eb
def test_enabled_without_effort_emits_nothing(self, custom_profile):
"""enabled but no effort → omit; do NOT force a level the user didn't pick."""
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": True}, model="glm-5.2"
)
assert eb == {}
assert tl == {}
def test_does_not_force_think_true_on_enable(self, custom_profile):
"""We must never send think=True on enable — it's Ollama-only and
would 400 on GLM/vLLM endpoints that don't recognize it."""
eb, _ = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": "high"}, model="glm-5.2"
)
assert eb.get("think") is not True
class TestCustomReasoningWithNumCtx:
"""Ollama num_ctx and reasoning are independent and compose."""
def test_num_ctx_alone(self, custom_profile):
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config=None, ollama_num_ctx=8192, model="qwen3"
)
assert eb == {"options": {"num_ctx": 8192}}
assert tl == {}
def test_num_ctx_with_effort(self, custom_profile):
eb, tl = custom_profile.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": "high"},
ollama_num_ctx=8192,
model="qwen3",
)
assert eb == {"options": {"num_ctx": 8192}}
assert tl == {"reasoning_effort": "high"}