mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
LM Studio's request vocabulary tops out at "xhigh", but Hermes' generic
effort ladder has since grown two stronger levels. "max" and "ultra" miss
the _LM_VALID_EFFORTS membership test, keep the initialized "medium"
default, and are thereby conflated with unparseable input -- so asking for
more reasoning yields less than "xhigh":
high -> 'high' xhigh -> 'xhigh'
max -> 'medium' ultra -> 'medium'
This is drift, not a design choice. The valid set was an exact mirror of
VALID_REASONING_EFFORTS when the file was authored; the ladder then grew
"max" and later "ultra", and the sweep that taught every other provider
about the new levels missed this module -- it has never been touched since
it was written.
Clamp the two stronger levels onto LM Studio's declared ceiling instead,
mirroring the ceiling clamp every other provider already applies. Widening
_LM_VALID_EFFORTS would instead assert that LM Studio accepts "max" on the
wire, which is a provider-side claim this repo cannot verify; clamping
consumes only the ceiling the file already declares for itself.
The clamp is kept separate from _LM_EFFORT_ALIASES because that mapping is
also applied to the model's published allowed_options, which must not be
rewritten. A clamped value stays subject to the allowed_options check, so a
model that does not publish "xhigh" still gets the field omitted and falls
back to its own default -- exactly how a directly-requested "xhigh" behaves.
The regression test asserts monotonicity over the canonical ladder rather
than the two values alone, so the next level added upstream cannot silently
reintroduce the inversion.
60 lines
2.6 KiB
Python
60 lines
2.6 KiB
Python
"""LM Studio reasoning-effort resolution shared by the chat-completions
|
|
transport and run_agent's iteration-limit summary path.
|
|
|
|
LM Studio publishes per-model ``capabilities.reasoning.allowed_options`` (e.g.
|
|
``["off","on"]`` for toggle-style models, ``["off","minimal","low"]`` for
|
|
graduated models). We map the user's ``reasoning_config`` onto LM Studio's
|
|
OpenAI-compatible vocabulary, then clamp against the model's allowed set so
|
|
the server doesn't 400 on an unsupported effort.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import List, Optional
|
|
|
|
# LM Studio accepts these top-level reasoning_effort values via its
|
|
# OpenAI-compatible chat.completions endpoint.
|
|
_LM_VALID_EFFORTS = {"none", "minimal", "low", "medium", "high", "xhigh"}
|
|
|
|
# Toggle-style models publish allowed_options as ["off","on"] in /api/v1/models.
|
|
# Map them onto the OpenAI-compatible request vocabulary.
|
|
_LM_EFFORT_ALIASES = {"off": "none", "on": "medium"}
|
|
|
|
# Hermes' generic effort ladder grew past LM Studio's vocabulary ("max",
|
|
# "ultra"). Clamp the stronger generic levels onto LM Studio's ceiling: left
|
|
# alone they miss _LM_VALID_EFFORTS, keep the initialized "medium" default and
|
|
# are thereby conflated with unparseable input, so asking for more reasoning
|
|
# yields less than "xhigh". Mirrors the ceiling clamp every other provider
|
|
# applies (see agent/transports/codex.py).
|
|
#
|
|
# Deliberately separate from _LM_EFFORT_ALIASES: that mapping is also applied
|
|
# to the model's published allowed_options, which must not be rewritten.
|
|
_LM_EFFORT_CLAMP = {"max": "xhigh", "ultra": "xhigh"}
|
|
|
|
|
|
def resolve_lmstudio_effort(
|
|
reasoning_config: Optional[dict],
|
|
allowed_options: Optional[List[str]],
|
|
) -> Optional[str]:
|
|
"""Return the ``reasoning_effort`` string to send to LM Studio, or ``None``.
|
|
|
|
``None`` means "omit the field": the user picked a level the model can't
|
|
honor, so let LM Studio fall back to the model's declared default rather
|
|
than silently substituting a different effort. When ``allowed_options`` is
|
|
falsy (probe failed), skip clamping and send the resolved effort anyway.
|
|
"""
|
|
effort = "medium"
|
|
if reasoning_config and isinstance(reasoning_config, dict):
|
|
if reasoning_config.get("enabled") is False:
|
|
effort = "none"
|
|
else:
|
|
raw = (reasoning_config.get("effort") or "").strip().lower()
|
|
raw = _LM_EFFORT_ALIASES.get(raw, raw)
|
|
raw = _LM_EFFORT_CLAMP.get(raw, raw)
|
|
if raw in _LM_VALID_EFFORTS:
|
|
effort = raw
|
|
if allowed_options:
|
|
allowed = {_LM_EFFORT_ALIASES.get(opt, opt) for opt in allowed_options}
|
|
if effort not in allowed:
|
|
return None
|
|
return effort
|