feat(auxiliary): per-task reasoning_effort for auxiliary models (#64597)
Every auxiliary task block (vision, web_extract, compression,
title_generation, curator, background_review, moa_reference, ...) now
accepts a reasoning_effort shorthand:
auxiliary:
compression:
reasoning_effort: low
vision:
reasoning_effort: none
_get_task_extra_body() folds it into extra_body.reasoning, which every
auxiliary wire already translates: chat.completions passes it through,
the Codex Responses adapter maps it to top-level reasoning/include, and
the Anthropic auxiliary adapter now forwards it into
build_anthropic_kwargs(reasoning_config=...) (previously hardcoded None).
An explicit extra_body.reasoning on the same task wins over the
shorthand. Invalid levels are ignored with a warning. Empty string
(the shipped default) is a no-op — zero behavior change.
Config: reasoning_effort added to all 16 auxiliary task blocks in
DEFAULT_CONFIG (no version bump — deep-merge handles new keys).
fix/verification-admin-route-recovery
parent
f7198a2055
commit
df5700ebe3
|
|
@ -1306,12 +1306,24 @@ class _AnthropicCompletionsAdapter:
|
||||||
elif choice_type in {"auto", "required", "none"}:
|
elif choice_type in {"auto", "required", "none"}:
|
||||||
normalized_tool_choice = choice_type
|
normalized_tool_choice = choice_type
|
||||||
|
|
||||||
|
# Honor extra_body.reasoning (chat.completions shape) so auxiliary
|
||||||
|
# tasks configured with auxiliary.<task>.reasoning_effort (or an
|
||||||
|
# explicit extra_body.reasoning) control Anthropic thinking too —
|
||||||
|
# build_anthropic_kwargs translates the config dict into the native
|
||||||
|
# ``thinking`` field and handles models where thinking is mandatory.
|
||||||
|
_reasoning_cfg = None
|
||||||
|
_eb = kwargs.get("extra_body")
|
||||||
|
if isinstance(_eb, dict):
|
||||||
|
_rc = _eb.get("reasoning")
|
||||||
|
if isinstance(_rc, dict):
|
||||||
|
_reasoning_cfg = _rc
|
||||||
|
|
||||||
anthropic_kwargs = build_anthropic_kwargs(
|
anthropic_kwargs = build_anthropic_kwargs(
|
||||||
model=model,
|
model=model,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
reasoning_config=None,
|
reasoning_config=_reasoning_cfg,
|
||||||
tool_choice=normalized_tool_choice,
|
tool_choice=normalized_tool_choice,
|
||||||
is_oauth=self._is_oauth,
|
is_oauth=self._is_oauth,
|
||||||
)
|
)
|
||||||
|
|
@ -6141,12 +6153,34 @@ def _effective_aux_timeout(task: str, timeout: Optional[float]) -> float:
|
||||||
|
|
||||||
|
|
||||||
def _get_task_extra_body(task: str) -> Dict[str, Any]:
|
def _get_task_extra_body(task: str) -> Dict[str, Any]:
|
||||||
"""Read auxiliary.<task>.extra_body and return a shallow copy when valid."""
|
"""Read auxiliary.<task>.extra_body and return a shallow copy when valid.
|
||||||
|
|
||||||
|
Also folds in ``auxiliary.<task>.reasoning_effort`` as an
|
||||||
|
``extra_body.reasoning`` config dict ({"enabled": ..., "effort": ...})
|
||||||
|
when set. An explicit ``extra_body.reasoning`` in config wins over the
|
||||||
|
``reasoning_effort`` shorthand (it is the more specific wire control).
|
||||||
|
Downstream, each wire already translates ``extra_body.reasoning``:
|
||||||
|
chat.completions passes it through, the Codex Responses adapter maps it
|
||||||
|
to top-level ``reasoning``/``include``, and the Anthropic auxiliary
|
||||||
|
client maps it to ``build_anthropic_kwargs(reasoning_config=...)``.
|
||||||
|
"""
|
||||||
task_config = _get_auxiliary_task_config(task)
|
task_config = _get_auxiliary_task_config(task)
|
||||||
raw = task_config.get("extra_body")
|
raw = task_config.get("extra_body")
|
||||||
if isinstance(raw, dict):
|
result = dict(raw) if isinstance(raw, dict) else {}
|
||||||
return dict(raw)
|
if "reasoning" not in result:
|
||||||
return {}
|
effort = task_config.get("reasoning_effort")
|
||||||
|
if effort is not None and effort != "":
|
||||||
|
from hermes_constants import parse_reasoning_effort
|
||||||
|
parsed = parse_reasoning_effort(effort)
|
||||||
|
if parsed is not None:
|
||||||
|
result["reasoning"] = parsed
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"auxiliary.%s.reasoning_effort %r is not a valid level "
|
||||||
|
"(none, minimal, low, medium, high, xhigh, max, ultra) — ignoring",
|
||||||
|
task, effort,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -506,11 +506,19 @@ prompt_caching:
|
||||||
# timeout: 30 # LLM API call timeout (seconds)
|
# timeout: 30 # LLM API call timeout (seconds)
|
||||||
# download_timeout: 30 # Image HTTP download timeout (seconds)
|
# download_timeout: 30 # Image HTTP download timeout (seconds)
|
||||||
# # Increase for slow connections or self-hosted image servers
|
# # Increase for slow connections or self-hosted image servers
|
||||||
|
# reasoning_effort: "" # Per-task thinking level: none, minimal, low, medium,
|
||||||
|
# # high, xhigh, max, ultra. Empty = provider default.
|
||||||
|
# # Works on every auxiliary task block (vision,
|
||||||
|
# # web_extract, compression, title_generation, curator,
|
||||||
|
# # background_review, moa_reference, ...). Example: run
|
||||||
|
# # compression at "low" and vision at "none" to cut
|
||||||
|
# # side-task latency/cost on reasoning models.
|
||||||
#
|
#
|
||||||
# # Web page scraping / summarization + browser page text extraction
|
# # Web page scraping / summarization + browser page text extraction
|
||||||
# web_extract:
|
# web_extract:
|
||||||
# provider: "auto"
|
# provider: "auto"
|
||||||
# model: ""
|
# model: ""
|
||||||
|
# reasoning_effort: "low"
|
||||||
#
|
#
|
||||||
# # Gemini 3.1 TTS hidden audio-tag insertion
|
# # Gemini 3.1 TTS hidden audio-tag insertion
|
||||||
# tts_audio_tags:
|
# tts_audio_tags:
|
||||||
|
|
|
||||||
|
|
@ -1588,6 +1588,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "", # API key for base_url (falls back to OPENAI_API_KEY)
|
"api_key": "", # API key for base_url (falls back to OPENAI_API_KEY)
|
||||||
"timeout": 120, # seconds — LLM API call timeout; vision payloads need generous timeout
|
"timeout": 120, # seconds — LLM API call timeout; vision payloads need generous timeout
|
||||||
"extra_body": {}, # OpenAI-compatible provider-specific request fields
|
"extra_body": {}, # OpenAI-compatible provider-specific request fields
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
"download_timeout": 30, # seconds — image HTTP download timeout; increase for slow connections
|
"download_timeout": 30, # seconds — image HTTP download timeout; increase for slow connections
|
||||||
},
|
},
|
||||||
"web_extract": {
|
"web_extract": {
|
||||||
|
|
@ -1597,6 +1598,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 360, # seconds (6min) — per-attempt LLM summarization timeout; increase for slow local models
|
"timeout": 360, # seconds (6min) — per-attempt LLM summarization timeout; increase for slow local models
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"compression": {
|
"compression": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1605,6 +1607,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 120, # seconds — compression summarises large contexts; increase for local models
|
"timeout": 120, # seconds — compression summarises large contexts; increase for local models
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Note: session_search no longer uses an auxiliary LLM (PR #27590 —
|
# Note: session_search no longer uses an auxiliary LLM (PR #27590 —
|
||||||
# single-shape tool returns DB content directly). The old
|
# single-shape tool returns DB content directly). The old
|
||||||
|
|
@ -1617,6 +1620,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"approval": {
|
"approval": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1625,6 +1629,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1633,6 +1638,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"title_generation": {
|
"title_generation": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1641,6 +1647,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
"language": "",
|
"language": "",
|
||||||
},
|
},
|
||||||
"tts_audio_tags": {
|
"tts_audio_tags": {
|
||||||
|
|
@ -1650,6 +1657,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Triage specifier — flesh out a rough one-liner in the Kanban
|
# Triage specifier — flesh out a rough one-liner in the Kanban
|
||||||
# Triage column into a concrete spec, then promote it to ``todo``.
|
# Triage column into a concrete spec, then promote it to ``todo``.
|
||||||
|
|
@ -1663,6 +1671,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Kanban decomposer — decomposes a triage task into a graph of
|
# Kanban decomposer — decomposes a triage task into a graph of
|
||||||
# child tasks routed to specialist profiles by description.
|
# child tasks routed to specialist profiles by description.
|
||||||
|
|
@ -1676,6 +1685,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 180,
|
"timeout": 180,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Profile describer — auto-generates a 1-2 sentence description
|
# Profile describer — auto-generates a 1-2 sentence description
|
||||||
# of what a profile is good at. Invoked by
|
# of what a profile is good at. Invoked by
|
||||||
|
|
@ -1688,6 +1698,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 60,
|
"timeout": 60,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Curator — skill-usage review fork. Timeout is generous because the
|
# Curator — skill-usage review fork. Timeout is generous because the
|
||||||
# review pass can take several minutes on reasoning models (umbrella
|
# review pass can take several minutes on reasoning models (umbrella
|
||||||
|
|
@ -1701,6 +1712,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 600,
|
"timeout": 600,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Monitor — urgency/importance classifier used by the important-mail
|
# Monitor — urgency/importance classifier used by the important-mail
|
||||||
# monitor catalog automation (cron/scripts/classify_items.py). Scores
|
# monitor catalog automation (cron/scripts/classify_items.py). Scores
|
||||||
|
|
@ -1715,6 +1727,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 60,
|
"timeout": 60,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
# Background review — the post-turn self-improvement fork that decides
|
# Background review — the post-turn self-improvement fork that decides
|
||||||
# whether to save a memory / patch a skill. "auto" (default) = run on
|
# whether to save a memory / patch a skill. "auto" (default) = run on
|
||||||
|
|
@ -1734,6 +1747,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"moa_reference": {
|
"moa_reference": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1742,6 +1756,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 900,
|
"timeout": 900,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
"moa_aggregator": {
|
"moa_aggregator": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|
@ -1750,6 +1765,7 @@ DEFAULT_CONFIG = {
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"timeout": 900,
|
"timeout": 900,
|
||||||
"extra_body": {},
|
"extra_body": {},
|
||||||
|
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3242,6 +3242,122 @@ class TestAuxiliaryTaskExtraBody:
|
||||||
kwargs = client.chat.completions.create.call_args.kwargs
|
kwargs = client.chat.completions.create.call_args.kwargs
|
||||||
assert kwargs["extra_body"]["enable_thinking"] is True
|
assert kwargs["extra_body"]["enable_thinking"] is True
|
||||||
|
|
||||||
|
def test_reasoning_effort_shorthand_folds_into_extra_body(self):
|
||||||
|
"""auxiliary.<task>.reasoning_effort becomes extra_body.reasoning."""
|
||||||
|
client = MagicMock()
|
||||||
|
client.base_url = "https://api.example.com/v1"
|
||||||
|
client.chat.completions.create.return_value = MagicMock()
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"auxiliary": {
|
||||||
|
"session_search": {"reasoning_effort": "low"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("hermes_cli.config.load_config", return_value=config), patch(
|
||||||
|
"agent.auxiliary_client._get_cached_client",
|
||||||
|
return_value=(client, "glm-4.5-air"),
|
||||||
|
):
|
||||||
|
call_llm(
|
||||||
|
task="session_search",
|
||||||
|
messages=[{"role": "user", "content": "hello"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = client.chat.completions.create.call_args.kwargs
|
||||||
|
assert kwargs["extra_body"]["reasoning"] == {"enabled": True, "effort": "low"}
|
||||||
|
|
||||||
|
def test_reasoning_effort_none_disables(self):
|
||||||
|
client = MagicMock()
|
||||||
|
client.base_url = "https://api.example.com/v1"
|
||||||
|
client.chat.completions.create.return_value = MagicMock()
|
||||||
|
|
||||||
|
config = {"auxiliary": {"session_search": {"reasoning_effort": "none"}}}
|
||||||
|
|
||||||
|
with patch("hermes_cli.config.load_config", return_value=config), patch(
|
||||||
|
"agent.auxiliary_client._get_cached_client",
|
||||||
|
return_value=(client, "glm-4.5-air"),
|
||||||
|
):
|
||||||
|
call_llm(task="session_search", messages=[{"role": "user", "content": "hi"}])
|
||||||
|
|
||||||
|
kwargs = client.chat.completions.create.call_args.kwargs
|
||||||
|
assert kwargs["extra_body"]["reasoning"] == {"enabled": False}
|
||||||
|
|
||||||
|
def test_explicit_extra_body_reasoning_wins_over_shorthand(self):
|
||||||
|
"""config extra_body.reasoning beats the reasoning_effort shorthand."""
|
||||||
|
client = MagicMock()
|
||||||
|
client.base_url = "https://api.example.com/v1"
|
||||||
|
client.chat.completions.create.return_value = MagicMock()
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"auxiliary": {
|
||||||
|
"session_search": {
|
||||||
|
"reasoning_effort": "xhigh",
|
||||||
|
"extra_body": {"reasoning": {"effort": "none"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("hermes_cli.config.load_config", return_value=config), patch(
|
||||||
|
"agent.auxiliary_client._get_cached_client",
|
||||||
|
return_value=(client, "glm-4.5-air"),
|
||||||
|
):
|
||||||
|
call_llm(task="session_search", messages=[{"role": "user", "content": "hi"}])
|
||||||
|
|
||||||
|
kwargs = client.chat.completions.create.call_args.kwargs
|
||||||
|
assert kwargs["extra_body"]["reasoning"] == {"effort": "none"}
|
||||||
|
|
||||||
|
def test_invalid_reasoning_effort_ignored_with_warning(self, caplog):
|
||||||
|
client = MagicMock()
|
||||||
|
client.base_url = "https://api.example.com/v1"
|
||||||
|
client.chat.completions.create.return_value = MagicMock()
|
||||||
|
|
||||||
|
config = {"auxiliary": {"session_search": {"reasoning_effort": "warp9"}}}
|
||||||
|
|
||||||
|
with patch("hermes_cli.config.load_config", return_value=config), patch(
|
||||||
|
"agent.auxiliary_client._get_cached_client",
|
||||||
|
return_value=(client, "glm-4.5-air"),
|
||||||
|
), caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"):
|
||||||
|
call_llm(task="session_search", messages=[{"role": "user", "content": "hi"}])
|
||||||
|
|
||||||
|
kwargs = client.chat.completions.create.call_args.kwargs
|
||||||
|
assert "reasoning" not in (kwargs.get("extra_body") or {})
|
||||||
|
assert any("reasoning_effort" in rec.message for rec in caplog.records)
|
||||||
|
|
||||||
|
def test_empty_reasoning_effort_is_noop(self):
|
||||||
|
"""The DEFAULT_CONFIG ships reasoning_effort: '' — must add nothing."""
|
||||||
|
from agent.auxiliary_client import _get_task_extra_body
|
||||||
|
|
||||||
|
config = {"auxiliary": {"session_search": {"reasoning_effort": ""}}}
|
||||||
|
with patch("hermes_cli.config.load_config", return_value=config):
|
||||||
|
assert _get_task_extra_body("session_search") == {}
|
||||||
|
|
||||||
|
def test_anthropic_aux_client_forwards_extra_body_reasoning(self):
|
||||||
|
"""_AnthropicCompletionsAdapter passes extra_body.reasoning into
|
||||||
|
build_anthropic_kwargs as reasoning_config."""
|
||||||
|
from agent.auxiliary_client import _AnthropicCompletionsAdapter
|
||||||
|
|
||||||
|
adapter = _AnthropicCompletionsAdapter(MagicMock(), "claude-sonnet-4-6", is_oauth=False)
|
||||||
|
|
||||||
|
with patch("agent.anthropic_adapter.build_anthropic_kwargs",
|
||||||
|
return_value={"model": "claude-sonnet-4-6", "messages": [], "max_tokens": 64}) as mock_bak, \
|
||||||
|
patch("agent.anthropic_adapter.create_anthropic_message") as mock_create, \
|
||||||
|
patch("agent.transports.get_transport") as mock_gt:
|
||||||
|
mock_gt.return_value.normalize_response.return_value = MagicMock(
|
||||||
|
content="ok", tool_calls=None, reasoning=None, finish_reason="stop",
|
||||||
|
usage=None, provider_data=None,
|
||||||
|
)
|
||||||
|
adapter.create(
|
||||||
|
model="claude-sonnet-4-6",
|
||||||
|
messages=[{"role": "user", "content": "hi"}],
|
||||||
|
max_tokens=64,
|
||||||
|
extra_body={"reasoning": {"enabled": True, "effort": "low"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_bak.call_args.kwargs["reasoning_config"] == {
|
||||||
|
"enabled": True, "effort": "low",
|
||||||
|
}
|
||||||
|
mock_create.assert_called_once()
|
||||||
|
|
||||||
def test_no_warning_when_provider_is_custom(self, monkeypatch, caplog):
|
def test_no_warning_when_provider_is_custom(self, monkeypatch, caplog):
|
||||||
"""No warning when the provider is 'custom' — OPENAI_BASE_URL is expected."""
|
"""No warning when the provider is 'custom' — OPENAI_BASE_URL is expected."""
|
||||||
import agent.auxiliary_client as mod
|
import agent.auxiliary_client as mod
|
||||||
|
|
|
||||||
|
|
@ -974,6 +974,22 @@ Every model slot in Hermes — auxiliary tasks, compression, fallback — uses t
|
||||||
| `model` | Which model to request | provider's default |
|
| `model` | Which model to request | provider's default |
|
||||||
| `base_url` | Custom OpenAI-compatible endpoint (overrides provider) | not set |
|
| `base_url` | Custom OpenAI-compatible endpoint (overrides provider) | not set |
|
||||||
|
|
||||||
|
Auxiliary task blocks additionally accept a `reasoning_effort` knob:
|
||||||
|
|
||||||
|
| Key | What it does | Default |
|
||||||
|
|-----|-------------|---------|
|
||||||
|
| `reasoning_effort` | Thinking level for that task's LLM calls: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` | not set (provider default) |
|
||||||
|
|
||||||
|
This is the per-task counterpart of the global `agent.reasoning_effort`: run compression at `low` or vision at `none` to cut side-task latency and cost when your main model is an expensive reasoning model, without touching your main chat behavior. It works on every auxiliary task block (`vision`, `web_extract`, `compression`, `title_generation`, `curator`, `background_review`, `moa_reference`, `moa_aggregator`, ...), across all three auxiliary wire formats (chat completions, Codex Responses, Anthropic Messages). An explicit `extra_body.reasoning` on the same task wins over the shorthand.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auxiliary:
|
||||||
|
compression:
|
||||||
|
reasoning_effort: "low" # summaries don't need deep thinking
|
||||||
|
vision:
|
||||||
|
reasoning_effort: "none" # disable thinking for image description
|
||||||
|
```
|
||||||
|
|
||||||
When `base_url` is set, Hermes ignores the provider and calls that endpoint directly (using `api_key` or `OPENAI_API_KEY` for auth). When only `provider` is set, Hermes uses that provider's built-in auth and base URL.
|
When `base_url` is set, Hermes ignores the provider and calls that endpoint directly (using `api_key` or `OPENAI_API_KEY` for auth). When only `provider` is set, Hermes uses that provider's built-in auth and base URL.
|
||||||
|
|
||||||
Available providers for auxiliary tasks: `auto`, `main`, plus any provider in the [provider registry](/reference/environment-variables) — `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `gemini`, `qwen-oauth`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, `minimax-cn`, `minimax-oauth`, `deepseek`, `nvidia`, `xai`, `xai-oauth`, `ollama-cloud`, `alibaba`, `bedrock`, `huggingface`, `arcee`, `xiaomi`, `kilocode`, `opencode-zen`, `opencode-go`, `azure-foundry` — or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`).
|
Available providers for auxiliary tasks: `auto`, `main`, plus any provider in the [provider registry](/reference/environment-variables) — `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `gemini`, `qwen-oauth`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, `minimax-cn`, `minimax-oauth`, `deepseek`, `nvidia`, `xai`, `xai-oauth`, `ollama-cloud`, `alibaba`, `bedrock`, `huggingface`, `arcee`, `xiaomi`, `kilocode`, `opencode-zen`, `opencode-go`, `azure-foundry` — or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue