mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
feat(reasoning): add max and ultra effort levels (#62650)
This commit is contained in:
parent
62a76bd3d5
commit
7550c594ce
47 changed files with 175 additions and 65 deletions
|
|
@ -65,6 +65,7 @@ THINKING_BUDGET = {"xhigh": 32000, "high": 16000, "medium": 8000, "low": 4000}
|
|||
# maps to low on every model. See:
|
||||
# https://platform.claude.com/docs/en/about-claude/models/migration-guide
|
||||
ADAPTIVE_EFFORT_MAP = {
|
||||
"ultra": "max",
|
||||
"max": "max",
|
||||
"xhigh": "xhigh",
|
||||
"high": "high",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,20 @@ from agent.transports.base import ProviderTransport
|
|||
from agent.transports.types import NormalizedResponse, ToolCall, Usage
|
||||
|
||||
|
||||
def _reasoning_config_for_model(model: str, reasoning_config: dict | None) -> dict | None:
|
||||
"""Return the model's wire-compatible reasoning config."""
|
||||
if not isinstance(reasoning_config, dict):
|
||||
return reasoning_config
|
||||
if (
|
||||
"gpt-5.6" in (model or "").lower()
|
||||
and str(reasoning_config.get("effort") or "").strip().lower() == "ultra"
|
||||
):
|
||||
normalized = dict(reasoning_config)
|
||||
normalized["effort"] = "max"
|
||||
return normalized
|
||||
return reasoning_config
|
||||
|
||||
|
||||
def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> dict | None:
|
||||
"""Translate Hermes/OpenRouter-style reasoning config to Gemini thinkingConfig."""
|
||||
if reasoning_config is None or not isinstance(reasoning_config, dict):
|
||||
|
|
@ -52,7 +66,7 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) ->
|
|||
if normalized_model.startswith("gemini-2.5-"):
|
||||
return thinking_config
|
||||
|
||||
if effort not in {"minimal", "low", "medium", "high", "xhigh"}:
|
||||
if effort not in {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}:
|
||||
effort = "medium"
|
||||
|
||||
# Gemini 3 Flash documents low/medium/high thinking levels; Gemini 3 Pro
|
||||
|
|
@ -62,13 +76,13 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) ->
|
|||
if "flash" in normalized_model:
|
||||
if effort in {"minimal", "low"}:
|
||||
thinking_config["thinkingLevel"] = "low"
|
||||
elif effort in {"high", "xhigh"}:
|
||||
elif effort in {"high", "xhigh", "max", "ultra"}:
|
||||
thinking_config["thinkingLevel"] = "high"
|
||||
else:
|
||||
thinking_config["thinkingLevel"] = "medium"
|
||||
elif "pro" in normalized_model:
|
||||
thinking_config["thinkingLevel"] = (
|
||||
"high" if effort in {"high", "xhigh"} else "low"
|
||||
"high" if effort in {"high", "xhigh", "max", "ultra"} else "low"
|
||||
)
|
||||
|
||||
return thinking_config
|
||||
|
|
@ -364,7 +378,7 @@ class ChatCompletionsTransport(ProviderTransport):
|
|||
is_nvidia_nim = params.get("is_nvidia_nim", False)
|
||||
is_kimi = params.get("is_kimi", False)
|
||||
is_tokenhub = params.get("is_tokenhub", False)
|
||||
reasoning_config = params.get("reasoning_config")
|
||||
reasoning_config = _reasoning_config_for_model(model, params.get("reasoning_config"))
|
||||
|
||||
if ephemeral is not None and max_tokens_fn:
|
||||
api_kwargs.update(max_tokens_fn(ephemeral))
|
||||
|
|
@ -563,7 +577,7 @@ class ChatCompletionsTransport(ProviderTransport):
|
|||
api_kwargs["max_tokens"] = anthropic_max
|
||||
|
||||
# Provider-specific api_kwargs extras (reasoning_effort, metadata, etc.)
|
||||
reasoning_config = params.get("reasoning_config")
|
||||
reasoning_config = _reasoning_config_for_model(model, params.get("reasoning_config"))
|
||||
extra_body_from_profile, top_level_from_profile = (
|
||||
profile.build_api_kwargs_extras(
|
||||
reasoning_config=reasoning_config,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,12 @@ class ResponsesApiTransport(ProviderTransport):
|
|||
reasoning_effort = reasoning_config["effort"]
|
||||
|
||||
_effort_clamp = {"minimal": "low"}
|
||||
if "gpt-5.6" in (model or "").lower():
|
||||
# Ultra is the Codex product tier; the Responses API wire value is max.
|
||||
_effort_clamp["ultra"] = "max"
|
||||
if params.get("is_xai_responses", False):
|
||||
# xAI Responses tops out at high; keep generic stronger values usable.
|
||||
_effort_clamp.update({"xhigh": "high", "max": "high", "ultra": "high"})
|
||||
reasoning_effort = _effort_clamp.get(reasoning_effort, reasoning_effort)
|
||||
|
||||
response_tools = _responses_tools(tools)
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ export const ENUM_OPTIONS: Record<string, string[]> = {
|
|||
'approvals.mode': ['manual', 'smart', 'off'],
|
||||
'code_execution.mode': ['project', 'strict'],
|
||||
'context.engine': ['compressor', 'default', 'custom'],
|
||||
'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
'memory.provider': ['', 'builtin', 'hindsight', 'honcho'],
|
||||
// Terminal execution backends — kept in sync with the dispatch ladder in
|
||||
// tools/terminal_tool.py::_create_environment (local/docker/singularity/
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export function ModelSettingsSkeleton() {
|
|||
|
||||
// Hermes' reasoning levels (VALID_REASONING_EFFORTS); `none` = thinking off.
|
||||
// Empty config = Hermes default (medium), shown as Medium.
|
||||
const EFFORT_VALUES = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'] as const
|
||||
const EFFORT_VALUES = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const
|
||||
|
||||
// agent.service_tier stores "fast"/"priority"/"on" for fast; anything else is
|
||||
// normal (mirrors tui_gateway _load_service_tier).
|
||||
|
|
@ -93,8 +93,8 @@ const isFastTier = (tier: unknown): boolean =>
|
|||
.toLowerCase()
|
||||
)
|
||||
|
||||
// Reuse the composer's effort labels (`xhigh` shows as "Max", else 1:1).
|
||||
const effortLabelKey = (v: string) => (v === 'xhigh' ? 'max' : v) as 'high' | 'low' | 'max' | 'medium' | 'minimal'
|
||||
// Reuse the composer's effort labels.
|
||||
const effortLabelKey = (v: string) => v as 'high' | 'low' | 'max' | 'medium' | 'minimal' | 'ultra' | 'xhigh'
|
||||
|
||||
// A provider row is "ready" to pick a model from when it reports models. The
|
||||
// backend now surfaces the full `hermes model` universe (every canonical
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ const EFFORT_OPTIONS = [
|
|||
{ value: 'low', labelKey: 'low' },
|
||||
{ value: 'medium', labelKey: 'medium' },
|
||||
{ value: 'high', labelKey: 'high' },
|
||||
{ value: 'xhigh', labelKey: 'max' }
|
||||
{ value: 'xhigh', labelKey: 'xhigh' },
|
||||
{ value: 'max', labelKey: 'max' },
|
||||
{ value: 'ultra', labelKey: 'ultra' }
|
||||
] as const
|
||||
|
||||
/** How "fast" is achieved for a given model — two different mechanisms:
|
||||
|
|
|
|||
|
|
@ -2039,7 +2039,9 @@ export const en: Translations = {
|
|||
low: 'Low',
|
||||
medium: 'Medium',
|
||||
high: 'High',
|
||||
xhigh: 'Extra High',
|
||||
max: 'Max',
|
||||
ultra: 'Ultra',
|
||||
updateFailed: 'Model option update failed',
|
||||
fastFailed: 'Fast mode update failed'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1984,7 +1984,9 @@ export const ja = defineLocale({
|
|||
low: '低',
|
||||
medium: '中',
|
||||
high: '高',
|
||||
xhigh: '特高',
|
||||
max: '最大',
|
||||
ultra: 'ウルトラ',
|
||||
updateFailed: 'モデルオプションの更新に失敗しました',
|
||||
fastFailed: '高速モードの更新に失敗しました'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1671,7 +1671,9 @@ export interface Translations {
|
|||
low: string
|
||||
medium: string
|
||||
high: string
|
||||
xhigh: string
|
||||
max: string
|
||||
ultra: string
|
||||
updateFailed: string
|
||||
fastFailed: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1919,7 +1919,9 @@ export const zhHant = defineLocale({
|
|||
low: '低',
|
||||
medium: '中',
|
||||
high: '高',
|
||||
xhigh: '極高',
|
||||
max: '最高',
|
||||
ultra: '超高',
|
||||
updateFailed: '模型選項更新失敗',
|
||||
fastFailed: '快速模式更新失敗'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2203,7 +2203,9 @@ export const zh: Translations = {
|
|||
low: '低',
|
||||
medium: '中',
|
||||
high: '高',
|
||||
xhigh: '极高',
|
||||
max: '最高',
|
||||
ultra: '超高',
|
||||
updateFailed: '模型选项更新失败',
|
||||
fastFailed: '快速模式更新失败'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ describe('model-status-label', () => {
|
|||
|
||||
it('maps reasoning effort to compact labels', () => {
|
||||
expect(reasoningEffortLabel('high')).toBe('High')
|
||||
expect(reasoningEffortLabel('xhigh')).toBe('Max')
|
||||
expect(reasoningEffortLabel('xhigh')).toBe('XHigh')
|
||||
expect(reasoningEffortLabel('max')).toBe('Max')
|
||||
expect(reasoningEffortLabel('ultra')).toBe('Ultra')
|
||||
expect(reasoningEffortLabel('')).toBe('')
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ const REASONING_LABELS: Record<string, string> = {
|
|||
low: 'Low',
|
||||
medium: 'Med',
|
||||
high: 'High',
|
||||
xhigh: 'Max'
|
||||
xhigh: 'XHigh',
|
||||
max: 'Max',
|
||||
ultra: 'Ultra'
|
||||
}
|
||||
|
||||
export function reasoningEffortLabel(effort: string): string {
|
||||
|
|
|
|||
|
|
@ -1192,7 +1192,7 @@ def main(
|
|||
providers_order (str): Comma-separated list of OpenRouter providers to try in order (e.g. "anthropic,openai,google")
|
||||
provider_sort (str): Sort providers by "price", "throughput", or "latency" (OpenRouter only)
|
||||
max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set)
|
||||
reasoning_effort (str): OpenRouter reasoning effort level: "none", "minimal", "low", "medium", "high", "xhigh" (default: "medium")
|
||||
reasoning_effort (str): Reasoning effort: "none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra" (default: "medium")
|
||||
reasoning_disabled (bool): Completely disable reasoning/thinking tokens (default: False)
|
||||
prefill_messages_file (str): Path to JSON file containing prefill messages (list of {role, content} dicts)
|
||||
max_samples (int): Only process the first N samples from the dataset (optional, processes all if not set)
|
||||
|
|
@ -1261,7 +1261,7 @@ def main(
|
|||
print("🧠 Reasoning: DISABLED (effort=none)")
|
||||
elif reasoning_effort:
|
||||
# Use specified effort level
|
||||
valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"]
|
||||
valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]
|
||||
if reasoning_effort not in valid_efforts:
|
||||
print(f"❌ Error: --reasoning_effort must be one of: {', '.join(valid_efforts)}")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4792,7 +4792,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
"""Load reasoning effort from config.yaml.
|
||||
|
||||
Reads agent.reasoning_effort from config.yaml. Valid: "none",
|
||||
"minimal", "low", "medium", "high", "xhigh". Returns None to use
|
||||
"minimal", "low", "medium", "high", "xhigh", "max", "ultra". Returns None to use
|
||||
default (medium).
|
||||
"""
|
||||
from hermes_constants import parse_reasoning_effort
|
||||
|
|
|
|||
|
|
@ -2733,7 +2733,7 @@ class GatewaySlashCommandsMixin:
|
|||
return t("gateway.reasoning.reset_done")
|
||||
if effort == "none":
|
||||
parsed = {"enabled": False}
|
||||
elif effort in {"minimal", "low", "medium", "high", "xhigh"}:
|
||||
elif effort in {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}:
|
||||
parsed = {"enabled": True, "effort": effort}
|
||||
else:
|
||||
return t(
|
||||
|
|
|
|||
|
|
@ -2471,7 +2471,7 @@ class CLICommandsMixin:
|
|||
|
||||
Usage:
|
||||
/reasoning Show current effort level and display state
|
||||
/reasoning <level> Set reasoning effort (none, minimal, low, medium, high, xhigh)
|
||||
/reasoning <level> Set effort (none, minimal, low, medium, high, xhigh, max, ultra)
|
||||
/reasoning show|on Show model thinking/reasoning in output
|
||||
/reasoning hide|off Hide model thinking/reasoning from output
|
||||
/reasoning full Show complete thinking (no 10-line clamp)
|
||||
|
|
@ -2493,7 +2493,7 @@ class CLICommandsMixin:
|
|||
full_state = "full" if getattr(self, "reasoning_full", False) else "clamped to 10 lines"
|
||||
_cprint(f" {_ACCENT}Reasoning effort: {level}{_RST}")
|
||||
_cprint(f" {_ACCENT}Reasoning display: {display_state} ({full_state}){_RST}")
|
||||
_cprint(f" {_DIM}Usage: /reasoning <none|minimal|low|medium|high|xhigh|show|hide|full|clamp>{_RST}")
|
||||
_cprint(f" {_DIM}Usage: /reasoning <none|minimal|low|medium|high|xhigh|max|ultra|show|hide|full|clamp>{_RST}")
|
||||
return
|
||||
|
||||
arg = parts[1].strip().lower()
|
||||
|
|
@ -2534,7 +2534,7 @@ class CLICommandsMixin:
|
|||
parsed = _parse_reasoning_config(arg)
|
||||
if parsed is None:
|
||||
_cprint(f" {_DIM}(._.) Unknown argument: {arg}{_RST}")
|
||||
_cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh{_RST}")
|
||||
_cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh, max, ultra{_RST}")
|
||||
_cprint(f" {_DIM}Display: show, hide{_RST}")
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
|||
"Configuration"),
|
||||
CommandDef("reasoning", "Manage reasoning effort and display", "Configuration",
|
||||
args_hint="[level|show|hide|full|clamp]",
|
||||
subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "show", "hide", "on", "off", "full", "clamp")),
|
||||
subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra", "show", "hide", "on", "off", "full", "clamp")),
|
||||
CommandDef("fast", "Toggle fast mode — OpenAI Priority Processing / Anthropic Fast Mode (Normal/Fast)", "Configuration",
|
||||
args_hint="[normal|fast|status]",
|
||||
subcommands=("normal", "fast", "status", "on", "off")),
|
||||
|
|
|
|||
|
|
@ -2240,8 +2240,8 @@ DEFAULT_CONFIG = {
|
|||
# (API, tools, iteration budget), never a delegation
|
||||
# stopwatch. Set a positive number of seconds
|
||||
# (floor 30s) to enforce a hard cap.
|
||||
"reasoning_effort": "", # reasoning effort for subagents: "xhigh", "high", "medium",
|
||||
# "low", "minimal", "none" (empty = inherit parent's level)
|
||||
"reasoning_effort": "", # subagent effort: "ultra", "max", "xhigh", "high",
|
||||
# "medium", "low", "minimal", "none" (empty = inherit)
|
||||
"max_concurrent_children": 3, # unified concurrency cap: max parallel children per batch
|
||||
# AND max concurrent background (background=true)
|
||||
# delegation units. New async dispatches beyond the cap
|
||||
|
|
|
|||
|
|
@ -3925,7 +3925,7 @@ def _prompt_reasoning_effort_selection(efforts, current_effort=""):
|
|||
str(effort).strip().lower() for effort in efforts if str(effort).strip()
|
||||
)
|
||||
)
|
||||
canonical_order = ("minimal", "low", "medium", "high", "xhigh")
|
||||
canonical_order = ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")
|
||||
ordered = [effort for effort in canonical_order if effort in deduped]
|
||||
ordered.extend(effort for effort in deduped if effort not in canonical_order)
|
||||
if not ordered:
|
||||
|
|
|
|||
|
|
@ -696,7 +696,7 @@ _SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = {
|
|||
"delegation.reasoning_effort": {
|
||||
"type": "select",
|
||||
"description": "Reasoning effort for delegated subagents",
|
||||
"options": ["", "low", "medium", "high"],
|
||||
"options": ["", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"],
|
||||
},
|
||||
"updates.non_interactive_local_changes": {
|
||||
"type": "select",
|
||||
|
|
|
|||
|
|
@ -791,13 +791,16 @@ def apply_subprocess_home_env(env: dict[str, str]) -> None:
|
|||
env["HOME"] = home
|
||||
|
||||
|
||||
VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max")
|
||||
VALID_REASONING_EFFORTS = (
|
||||
"minimal", "low", "medium", "high", "xhigh", "max", "ultra",
|
||||
)
|
||||
|
||||
|
||||
def parse_reasoning_effort(effort) -> dict | None:
|
||||
"""Parse a reasoning effort level into a config dict.
|
||||
|
||||
Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max".
|
||||
Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max",
|
||||
"ultra".
|
||||
Returns None when the input is empty or unrecognized (caller uses default).
|
||||
Returns {"enabled": False} for "none" (aliases: "false", "disabled", and
|
||||
YAML boolean False — users write ``reasoning_effort: false``/``off``/``no``
|
||||
|
|
|
|||
BIN
infographic/reasoning-max-ultra/infographic.png
Normal file
BIN
infographic/reasoning-max-ultra/infographic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
|
|
@ -35,8 +35,8 @@ class CopilotProfile(ProviderProfile):
|
|||
supported_efforts = github_model_reasoning_efforts(model)
|
||||
if supported_efforts and reasoning_config:
|
||||
effort = reasoning_config.get("effort", "medium")
|
||||
# Normalize non-standard effort levels to the nearest supported
|
||||
if effort == "xhigh":
|
||||
# Normalize stronger generic levels to the nearest supported.
|
||||
if effort in {"xhigh", "max", "ultra"}:
|
||||
effort = "high"
|
||||
if effort in supported_efforts:
|
||||
extra_body["reasoning"] = {"effort": effort}
|
||||
|
|
|
|||
|
|
@ -69,12 +69,12 @@ class DeepSeekProfile(ProviderProfile):
|
|||
if not enabled:
|
||||
return extra_body, top_level
|
||||
|
||||
# Effort mapping. Pass low/medium/high through; xhigh/max → max.
|
||||
# Effort mapping. Pass low/medium/high through; stronger levels → max.
|
||||
# When no effort is set we omit reasoning_effort so DeepSeek applies
|
||||
# its server default (currently high).
|
||||
if isinstance(reasoning_config, dict):
|
||||
effort = (reasoning_config.get("effort") or "").strip().lower()
|
||||
if effort in {"xhigh", "max"}:
|
||||
if effort in {"xhigh", "max", "ultra"}:
|
||||
top_level["reasoning_effort"] = "max"
|
||||
elif effort in {"low", "medium", "high"}:
|
||||
top_level["reasoning_effort"] = effort
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class OllamaCloudProfile(ProviderProfile):
|
|||
return {}, {}
|
||||
if effort == "none":
|
||||
return {}, {} # explicit none → suppress thinking
|
||||
if effort in ("xhigh", "max"):
|
||||
if effort in ("xhigh", "max", "ultra"):
|
||||
top_level["reasoning_effort"] = "max"
|
||||
elif effort in ("low", "medium", "high"):
|
||||
top_level["reasoning_effort"] = effort
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class OpenCodeGoProfile(ProviderProfile):
|
|||
effort = (reasoning_config.get("effort") or "").strip().lower()
|
||||
if not effort or effort == "none":
|
||||
return extra_body, top_level
|
||||
top_level["reasoning_effort"] = "max" if effort in {"xhigh", "max"} else "high"
|
||||
top_level["reasoning_effort"] = "max" if effort in {"xhigh", "max", "ultra"} else "high"
|
||||
return extra_body, top_level
|
||||
|
||||
if _is_kimi_k2_model(model):
|
||||
|
|
@ -90,7 +90,7 @@ class OpenCodeGoProfile(ProviderProfile):
|
|||
return extra_body, top_level
|
||||
|
||||
effort = (reasoning_config.get("effort") or "").strip().lower()
|
||||
if effort in {"xhigh", "max"}:
|
||||
if effort in {"xhigh", "max", "ultra"}:
|
||||
top_level["reasoning_effort"] = "high"
|
||||
elif effort in {"low", "medium", "high"}:
|
||||
top_level["reasoning_effort"] = effort
|
||||
|
|
@ -114,7 +114,7 @@ class OpenCodeGoProfile(ProviderProfile):
|
|||
|
||||
if isinstance(reasoning_config, dict):
|
||||
effort = (reasoning_config.get("effort") or "").strip().lower()
|
||||
if effort in {"xhigh", "max"}:
|
||||
if effort in {"xhigh", "max", "ultra"}:
|
||||
top_level["reasoning_effort"] = "max"
|
||||
elif effort in {"low", "medium", "high"}:
|
||||
top_level["reasoning_effort"] = effort
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ def _is_glm_5_2(model: str | None) -> bool:
|
|||
def _glm_5_2_reasoning_effort(reasoning_config: dict | None) -> str | None:
|
||||
"""Map Hermes reasoning effort onto GLM-5.2's native ``high``/``max``.
|
||||
|
||||
GLM-5.2 only supports two enabled effort levels. ``xhigh``/``max``
|
||||
GLM-5.2 only supports two enabled effort levels. ``xhigh``/``max``/``ultra``
|
||||
request the top tier; everything else that is enabled requests ``high``
|
||||
(its minimum thinking level). When reasoning is explicitly disabled, or
|
||||
no effort preference is supplied, the server default is left untouched.
|
||||
|
|
@ -76,7 +76,7 @@ def _glm_5_2_reasoning_effort(reasoning_config: dict | None) -> str | None:
|
|||
if not effort or effort == "none":
|
||||
return None
|
||||
|
||||
if effort in {"xhigh", "max"}:
|
||||
if effort in {"xhigh", "max", "ultra"}:
|
||||
return "max"
|
||||
# low / medium / minimal / high all clamp to GLM-5.2's minimum: high.
|
||||
return "high"
|
||||
|
|
|
|||
|
|
@ -4085,7 +4085,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
await self._run_simple_slash(interaction, f"/model {name}".strip())
|
||||
|
||||
@tree.command(name="reasoning", description="Show or change reasoning effort")
|
||||
@discord.app_commands.describe(effort="Reasoning effort: none, minimal, low, medium, high, or xhigh.")
|
||||
@discord.app_commands.describe(effort="Effort: none, minimal, low, medium, high, xhigh, max, or ultra.")
|
||||
async def slash_reasoning(interaction: discord.Interaction, effort: str = ""):
|
||||
await self._run_simple_slash(interaction, f"/reasoning {effort}".strip())
|
||||
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ The registry of record is `hermes_cli/commands.py` — every consumer
|
|||
/config Show config (CLI)
|
||||
/model [name] Show or change model
|
||||
/personality [name] Set personality
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide)
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide)
|
||||
/verbose Cycle: off → new → all → verbose
|
||||
/voice [on|off|tts] Voice mode
|
||||
/yolo Toggle approval bypass
|
||||
|
|
|
|||
|
|
@ -1448,6 +1448,16 @@ class TestBuildAnthropicKwargs:
|
|||
assert kwargs["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert kwargs["output_config"] == {"effort": "xhigh"}
|
||||
|
||||
def test_reasoning_config_clamps_generic_ultra_to_anthropic_max(self):
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4.8",
|
||||
messages=[{"role": "user", "content": "think harder"}],
|
||||
tools=None,
|
||||
max_tokens=4096,
|
||||
reasoning_config={"enabled": True, "effort": "ultra"},
|
||||
)
|
||||
assert kwargs["output_config"] == {"effort": "max"}
|
||||
|
||||
def test_reasoning_config_maps_max_effort_for_4_7_models(self):
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4-7",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,23 @@ class TestChatCompletionsBasic:
|
|||
def test_registered(self, transport):
|
||||
assert transport is not None
|
||||
|
||||
@pytest.mark.parametrize("provider", ["nous", "openrouter"])
|
||||
def test_gpt56_ultra_uses_max_wire_effort(self, transport, provider):
|
||||
from providers import get_provider_profile
|
||||
|
||||
profile = get_provider_profile(provider)
|
||||
kw = transport.build_kwargs(
|
||||
model="openai/gpt-5.6-sol",
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
tools=[],
|
||||
reasoning_config={"enabled": True, "effort": "ultra"},
|
||||
supports_reasoning=True,
|
||||
provider_profile=profile,
|
||||
provider_name=provider,
|
||||
base_url=profile.base_url,
|
||||
)
|
||||
assert kw["extra_body"]["reasoning"] == {"enabled": True, "effort": "max"}
|
||||
|
||||
def test_convert_tools_identity(self, transport):
|
||||
tools = [{"type": "function", "function": {"name": "test", "parameters": {}}}]
|
||||
assert transport.convert_tools(tools) is tools
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ class TestCodexBuildKwargs:
|
|||
)
|
||||
assert kw.get("reasoning", {}).get("effort") == "high"
|
||||
|
||||
@pytest.mark.parametrize("effort, wire_effort", [("max", "max"), ("ultra", "max")])
|
||||
def test_extended_reasoning_efforts_use_api_wire_value(self, transport, effort, wire_effort):
|
||||
kw = transport.build_kwargs(
|
||||
model="gpt-5.6-sol",
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
tools=[],
|
||||
reasoning_config={"enabled": True, "effort": effort},
|
||||
)
|
||||
assert kw.get("reasoning", {}).get("effort") == wire_effort
|
||||
|
||||
def test_reasoning_disabled(self, transport):
|
||||
messages = [{"role": "user", "content": "Hi"}]
|
||||
kw = transport.build_kwargs(
|
||||
|
|
@ -401,6 +411,17 @@ class TestCodexBuildKwargs:
|
|||
# full history.
|
||||
assert "reasoning.encrypted_content" in kw.get("include", [])
|
||||
|
||||
@pytest.mark.parametrize("effort", ["xhigh", "max", "ultra"])
|
||||
def test_xai_stronger_generic_efforts_clamp_to_high(self, transport, effort):
|
||||
kw = transport.build_kwargs(
|
||||
model="grok-4.3",
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
tools=[],
|
||||
is_xai_responses=True,
|
||||
reasoning_config={"enabled": True, "effort": effort},
|
||||
)
|
||||
assert kw.get("reasoning") == {"effort": "high"}
|
||||
|
||||
def test_xai_injects_native_web_search_when_client_web_search_present(self, transport):
|
||||
"""xAI path swaps a client-side ``web_search`` function for xAI's
|
||||
native server-side ``web_search`` built-in so grok server-side search
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class TestParseReasoningConfig(unittest.TestCase):
|
|||
self.assertEqual(result, {"enabled": False})
|
||||
|
||||
def test_valid_levels(self):
|
||||
for level in ("low", "medium", "high", "xhigh", "minimal"):
|
||||
for level in ("low", "medium", "high", "xhigh", "max", "ultra", "minimal"):
|
||||
result = self._parse(level)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertTrue(result.get("enabled"))
|
||||
|
|
@ -41,7 +41,6 @@ class TestParseReasoningConfig(unittest.TestCase):
|
|||
self.assertIsNone(self._parse(" "))
|
||||
|
||||
def test_unknown_returns_none(self):
|
||||
self.assertIsNone(self._parse("ultra"))
|
||||
self.assertIsNone(self._parse("turbo"))
|
||||
|
||||
def test_case_insensitive(self):
|
||||
|
|
|
|||
|
|
@ -148,6 +148,29 @@ class TestReasoningCommand:
|
|||
assert runner._reasoning_config == {"enabled": True, "effort": "high"}
|
||||
assert "session only" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("effort", ["max", "ultra"])
|
||||
async def test_handle_reasoning_command_accepts_extended_efforts(
|
||||
self, tmp_path, monkeypatch, effort
|
||||
):
|
||||
hermes_home = tmp_path / "hermes"
|
||||
hermes_home.mkdir()
|
||||
(hermes_home / "config.yaml").write_text(
|
||||
"agent:\n reasoning_effort: medium\n", encoding="utf-8"
|
||||
)
|
||||
monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
|
||||
|
||||
runner = _make_runner()
|
||||
event = _make_event(f"/reasoning {effort}")
|
||||
session_key = runner._session_key_for_source(event.source)
|
||||
|
||||
await runner._handle_reasoning_command(event)
|
||||
|
||||
assert runner._session_reasoning_overrides[session_key] == {
|
||||
"enabled": True,
|
||||
"effort": effort,
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reasoning_global_clears_existing_session_override(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / "hermes"
|
||||
|
|
|
|||
|
|
@ -75,13 +75,15 @@ class TestCommandRegistry:
|
|||
|
||||
def test_reasoning_subcommands_are_in_logical_order(self):
|
||||
reasoning = next(cmd for cmd in COMMAND_REGISTRY if cmd.name == "reasoning")
|
||||
assert reasoning.subcommands[:6] == (
|
||||
assert reasoning.subcommands[:8] == (
|
||||
"none",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max",
|
||||
"ultra",
|
||||
)
|
||||
|
||||
def test_cli_only_and_gateway_only_are_mutually_exclusive(self):
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ class TestOllamaCloudReasoningEffort:
|
|||
|
||||
def test_unknown_effort_forwarded(self, ollama_cloud_profile):
|
||||
_, top_level = ollama_cloud_profile.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": True, "effort": "ultra"},
|
||||
reasoning_config={"enabled": True, "effort": "future-tier"},
|
||||
)
|
||||
assert top_level == {"reasoning_effort": "ultra"}
|
||||
assert top_level == {"reasoning_effort": "future-tier"}
|
||||
|
||||
|
||||
class TestOllamaCloudFullKwargsIntegration:
|
||||
|
|
|
|||
|
|
@ -314,12 +314,12 @@ class TestOpenRouterProfile:
|
|||
|
||||
Covers the full real config range produced by
|
||||
``hermes_constants.parse_reasoning_effort`` —
|
||||
``VALID_REASONING_EFFORTS = (minimal, low, medium, high, xhigh)``.
|
||||
``VALID_REASONING_EFFORTS`` (including max and ultra).
|
||||
"""
|
||||
p = get_provider_profile("openrouter")
|
||||
model = "anthropic/claude-fable-5"
|
||||
assert self._is_mandatory(model) # fixture really is mandatory
|
||||
for effort in ("minimal", "low", "medium", "high", "xhigh"):
|
||||
for effort in ("minimal", "low", "medium", "high", "xhigh", "max", "ultra"):
|
||||
eb, tl = p.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": True, "effort": effort},
|
||||
supports_reasoning=True,
|
||||
|
|
@ -342,14 +342,12 @@ class TestOpenRouterProfile:
|
|||
|
||||
def test_mandatory_anthropic_verbosity_is_value_agnostic_passthrough(self):
|
||||
"""The mapping passes the effort value through verbatim — it must NOT
|
||||
clamp or whitelist. ``xhigh`` is a real config value; ``max`` is not
|
||||
producible by ``parse_reasoning_effort`` today but OpenRouter accepts it
|
||||
for Claude (live-proven in #43432), so a forward value must survive
|
||||
clamp or whitelist. Extended values must survive
|
||||
rather than be silently dropped. The OpenAI SDK type only literals
|
||||
``low|medium|high`` but it's a TypedDict (no runtime validation), so the
|
||||
extended scale reaches the wire untouched."""
|
||||
p = get_provider_profile("openrouter")
|
||||
for effort in ("xhigh", "max"):
|
||||
for effort in ("xhigh", "max", "ultra"):
|
||||
_, tl = p.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": True, "effort": effort},
|
||||
supports_reasoning=True,
|
||||
|
|
|
|||
|
|
@ -483,10 +483,10 @@ class TestParseReasoningEffort:
|
|||
"""Guard against silently dropping a documented level.
|
||||
|
||||
The docstring promises "minimal", "low", "medium", "high", "xhigh",
|
||||
"max". If someone removes one from VALID_REASONING_EFFORTS without
|
||||
"max", "ultra". If someone removes one from VALID_REASONING_EFFORTS without
|
||||
updating the docstring, this test will fail and force the call out.
|
||||
"""
|
||||
documented = {"minimal", "low", "medium", "high", "xhigh", "max"}
|
||||
documented = {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}
|
||||
assert documented.issubset(set(VALID_REASONING_EFFORTS))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ describe("normalizeEffort", () => {
|
|||
});
|
||||
|
||||
it("passes through every valid effort level", () => {
|
||||
for (const level of ["none", "minimal", "low", "medium", "high", "xhigh"]) {
|
||||
for (const level of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]) {
|
||||
expect(normalizeEffort(level)).toBe(level);
|
||||
}
|
||||
});
|
||||
|
|
@ -26,7 +26,6 @@ describe("normalizeEffort", () => {
|
|||
|
||||
it("falls back to medium for unknown values", () => {
|
||||
expect(normalizeEffort("turbo")).toBe("medium");
|
||||
expect(normalizeEffort("max")).toBe("medium"); // 'max' is a label, not a value
|
||||
expect(normalizeEffort(42)).toBe("medium");
|
||||
});
|
||||
});
|
||||
|
|
@ -41,7 +40,7 @@ describe("EFFORT_OPTIONS", () => {
|
|||
it("covers the real reasoning levels plus thinking-off", () => {
|
||||
// Invariant against hermes_constants.VALID_REASONING_EFFORTS + 'none'.
|
||||
const values = new Set(EFFORT_OPTIONS.map((o) => o.value));
|
||||
for (const level of ["none", "minimal", "low", "medium", "high", "xhigh"]) {
|
||||
for (const level of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]) {
|
||||
expect(values.has(level)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ export const EFFORT_OPTIONS: ReadonlyArray<EffortOption> = [
|
|||
{ value: "low", label: "Low" },
|
||||
{ value: "medium", label: "Medium" },
|
||||
{ value: "high", label: "High" },
|
||||
{ value: "xhigh", label: "Max" },
|
||||
{ value: "xhigh", label: "Extra High" },
|
||||
{ value: "max", label: "Max" },
|
||||
{ value: "ultra", label: "Ultra" },
|
||||
];
|
||||
|
||||
export const VALID_EFFORTS: ReadonlySet<string> = new Set(
|
||||
|
|
|
|||
|
|
@ -1276,7 +1276,7 @@ Control how much "thinking" the model does before responding:
|
|||
|
||||
```yaml
|
||||
agent:
|
||||
reasoning_effort: "" # empty = medium (default). Options: none, minimal, low, medium, high, xhigh (max)
|
||||
reasoning_effort: "" # empty = medium. Options: none, minimal, low, medium, high, xhigh, max, ultra
|
||||
```
|
||||
|
||||
When unset (default), reasoning effort defaults to "medium" — a balanced level that works well for most tasks. Setting a value overrides it — higher reasoning effort gives better results on complex tasks at the cost of more tokens and latency.
|
||||
|
|
@ -1285,10 +1285,9 @@ When unset (default), reasoning effort defaults to "medium" — a balanced level
|
|||
These models use *adaptive* thinking and don't accept the usual `reasoning.effort`
|
||||
field — OpenRouter ignores it for them. Hermes transparently routes your
|
||||
`reasoning_effort` to OpenRouter's `verbosity` parameter instead (which maps to
|
||||
Anthropic's `output_config.effort`), so the same `low`/`medium`/`high`/`xhigh`
|
||||
knob keeps working — no extra configuration needed. `none` (or unset) leaves the
|
||||
model on its own adaptive default. (`max` is accepted on the wire but is not a
|
||||
selectable `reasoning_effort` value; `xhigh` is the configurable ceiling.) The
|
||||
Anthropic's `output_config.effort`), so the same effort knob keeps working with
|
||||
the levels supported by the selected model. `none` (or unset) leaves the model
|
||||
on its own adaptive default. The
|
||||
native Anthropic provider already controls effort directly and is unaffected.
|
||||
:::
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ Entries can optionally include:
|
|||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `--reasoning_effort` | Effort level: `none`, `minimal`, `low`, `medium`, `high`, `xhigh` |
|
||||
| `--reasoning_effort` | Reasoning effort: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` |
|
||||
| `--reasoning_disabled` | Completely disable reasoning/thinking tokens |
|
||||
|
||||
### Advanced Options
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ The registry of record is `hermes_cli/commands.py` — every consumer
|
|||
/config Show config (CLI)
|
||||
/model [name] Show or change model
|
||||
/personality [name] Set personality
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide)
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide)
|
||||
/verbose Cycle: off → new → all → verbose
|
||||
/voice [on|off|tts] Voice mode
|
||||
/yolo Toggle approval bypass
|
||||
|
|
|
|||
|
|
@ -1044,7 +1044,7 @@ auxiliary:
|
|||
|
||||
```yaml
|
||||
agent:
|
||||
reasoning_effort: "" # 空 = 中等(默认)。选项:none、minimal、low、medium、high、xhigh(最大)
|
||||
reasoning_effort: "" # 空 = 中等。选项:none、minimal、low、medium、high、xhigh、max、ultra
|
||||
```
|
||||
|
||||
未设置时(默认),推理努力程度默认为"medium" —— 适合大多数任务的平衡级别。设置值会覆盖它 —— 更高的推理努力程度在复杂任务上提供更好的结果,但代价是更多 token 和延迟。
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ python batch_runner.py --list_distributions
|
|||
|
||||
| 参数 | 说明 |
|
||||
|-----------|-------------|
|
||||
| `--reasoning_effort` | 推理力度:`none`、`minimal`、`low`、`medium`、`high`、`xhigh` |
|
||||
| `--reasoning_effort` | 推理力度:`none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`、`ultra` |
|
||||
| `--reasoning_disabled` | 完全禁用推理/思考 token |
|
||||
|
||||
### 高级选项
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ hermes uninstall Uninstall Hermes
|
|||
/config Show config (CLI)
|
||||
/model [name] Show or change model
|
||||
/personality [name] Set personality
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide)
|
||||
/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide)
|
||||
/verbose Cycle: off → new → all → verbose
|
||||
/voice [on|off|tts] Voice mode
|
||||
/yolo Toggle approval bypass
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue