mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
Three follow-up fixes to the salvaged reasoning_effort support, all verified
live against ollama.com /v1/chat/completions + /api/show on deepseek-v4-pro,
gemma3, and qwen3-coder:
1. Capability-gate on /api/show 'thinking'. The original ignored the
supports_reasoning flag and emitted reasoning_effort for every model. Now
gated: only models whose native /api/show capabilities list contains
'thinking' (deepseek-v4 yes; gemma3 / qwen3-coder no) get reasoning_effort.
Mirrors the LM Studio pattern — capability resolved once per (model,
base_url) in run_agent._supports_reasoning_extra_body via a cached probe
(hermes_cli.models.ollama_model_supports_thinking), threaded into the
profile hook as supports_reasoning. No live HTTP in the per-request path.
2. Disable actually disables. Ollama Cloud defaults to thinking ON and IGNORES
the extra_body.thinking:{type:disabled} shape (verified: still returned
reasoning). The only working off switch is top-level reasoning_effort:'none'.
The salvaged code returned ({}, {}) for enabled:false / effort:none, leaving
thinking ON. Now emits {'reasoning_effort': 'none'}.
3. Omit unrecognized effort. The original forwarded any unknown string verbatim
including 'minimal' (a real Hermes effort level). Ollama Cloud rejects
unrecognized values with a hard HTTP 400 (accepted set: low/medium/high/
max/none), so forwarding 'minimal' would break the request. Now omitted.
Core touches (run_agent.py, hermes_cli/models.py) add the capability probe;
the plugin profile only consumes the resolved flag. 24/24 profile tests green;
194 provider/transport tests unaffected.
89 lines
3.6 KiB
Python
89 lines
3.6 KiB
Python
"""Ollama Cloud provider profile.
|
||
|
||
Ollama Cloud's OpenAI-compatible ``/v1/chat/completions`` endpoint
|
||
supports top-level ``reasoning_effort`` with values ``none``, ``low``,
|
||
``medium``, ``high``, and ``max`` (the last being undocumented but
|
||
empirically confirmed for DeepSeek V4 — ``max`` produces ~2.5× more
|
||
thinking tokens than ``high``).
|
||
|
||
This profile maps Hermes's ``xhigh`` → ``max`` to unlock DeepSeek V4's
|
||
"Max thinking" tier through Ollama Cloud. ``low`` / ``medium`` / ``high``
|
||
pass through unchanged.
|
||
|
||
When reasoning is explicitly disabled (``enabled: false`` or
|
||
``effort: "none"``), ``reasoning_effort`` is omitted entirely so the
|
||
model runs in non-thinking mode.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from providers import register_provider
|
||
from providers.base import ProviderProfile
|
||
|
||
|
||
class OllamaCloudProfile(ProviderProfile):
|
||
"""Ollama Cloud — maps xhigh→max via top-level reasoning_effort."""
|
||
|
||
def build_api_kwargs_extras(
|
||
self,
|
||
*,
|
||
reasoning_config: dict | None = None,
|
||
supports_reasoning: bool = False,
|
||
**ctx: Any,
|
||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||
"""Emit top-level ``reasoning_effort`` for Ollama Cloud thinking models.
|
||
|
||
Gated on ``supports_reasoning``, which the transport resolves from the
|
||
model's native ``/api/show`` ``capabilities`` (``thinking``). Models
|
||
without the thinking capability (e.g. ``gemma3``, ``qwen3-coder``) get
|
||
no ``reasoning_effort`` at all — emitting it there is a no-op the API
|
||
ignores, and gating avoids sending a meaningless field.
|
||
"""
|
||
top_level: dict[str, Any] = {}
|
||
|
||
if not supports_reasoning:
|
||
return {}, {}
|
||
|
||
if reasoning_config and isinstance(reasoning_config, dict):
|
||
enabled = reasoning_config.get("enabled", True)
|
||
if enabled is False:
|
||
# Ollama Cloud defaults to thinking ON, and ignores the
|
||
# extra_body.thinking:{type:disabled} shape (verified live).
|
||
# The ONLY way to actually suppress thinking on its
|
||
# /v1/chat/completions endpoint is top-level
|
||
# reasoning_effort:"none" — omitting the field leaves
|
||
# thinking on.
|
||
return {}, {"reasoning_effort": "none"}
|
||
|
||
effort = (reasoning_config.get("effort") or "").strip().lower()
|
||
if not effort:
|
||
# No explicit effort requested — let the model decide
|
||
# (Ollama Cloud's server default is thinking ON).
|
||
return {}, {}
|
||
if effort == "none":
|
||
return {}, {"reasoning_effort": "none"} # explicit off switch
|
||
if effort in ("xhigh", "max", "ultra"):
|
||
top_level["reasoning_effort"] = "max"
|
||
elif effort in ("low", "medium", "high"):
|
||
top_level["reasoning_effort"] = effort
|
||
# Any other value (including "minimal", which Ollama Cloud's
|
||
# /v1/chat/completions rejects with HTTP 400 — its accepted set is
|
||
# {low, medium, high, max, none}) is omitted so the model applies
|
||
# its own default rather than triggering a hard 400. Matches the
|
||
# sibling deepseek / opencode-zen profiles, which target the same
|
||
# backend and omit unrecognized efforts rather than send garbage.
|
||
|
||
return {}, top_level
|
||
|
||
|
||
ollama_cloud = OllamaCloudProfile(
|
||
name="ollama-cloud",
|
||
aliases=("ollama_cloud",),
|
||
default_aux_model="nemotron-3-nano:30b",
|
||
env_vars=("OLLAMA_API_KEY",),
|
||
base_url="https://ollama.com/v1",
|
||
)
|
||
|
||
register_provider(ollama_cloud)
|