Files
hermes-agent/agent/lmstudio_reasoning.py
kshitijk4poor 72a346e8ea refactor(providers): one copy each of TokenHub's effort rule and the generic nested reasoning fallback
Review fold (round 1, simplify reuse + quality):
- tokenhub_effort() in agent/reasoning_effort.py: the tencent-tokenhub profile and the host-based
  branch base_url-only agents take used the same "high unless chosen, else clamp" expression in
  two places, so the two paths could drift apart.
- generic_nested_reasoning(): the OpenAI-compatible extra_body fallback auxiliary calls send when a
  profile declares no reasoning shape. The lmstudio profile's auxiliary branch was a line-for-line
  copy (it must produce it itself because overriding build_api_kwargs_extras turns the aux
  fallback off). Both now call the helper.
- agent/lmstudio_reasoning.py docstring names its real caller (the lmstudio profile).

Behaviour unchanged: 42/42 main-loop request cells and every auxiliary projection cell are
byte-identical to origin/main.
2026-10-07 11:27:35 +05:30

41 lines
1.9 KiB
Python

"""LM Studio reasoning-effort resolution (used by the lmstudio provider profile). LM Studio
publishes per-model
``capabilities.reasoning.allowed_options`` (``["off","on"]`` for toggle models,
``["off","minimal","low"]`` for graduated ones); the user's ``reasoning_config`` is
mapped onto LM Studio's vocabulary, then clamped to the allowed set so the server
doesn't 400."""
from __future__ import annotations
from typing import List, Optional
_LM_VALID_EFFORTS = {"none", "minimal", "low", "medium", "high", "xhigh"}
# Toggle vocabulary → request vocabulary; also applied to published allowed_options.
_LM_EFFORT_ALIASES = {"off": "none", "on": "medium"}
# Hermes' ladder grew past LM Studio's vocabulary ("max", "ultra"); without this
# ceiling clamp they'd fall to the "medium" default (more yields less than "xhigh").
# Separate from _LM_EFFORT_ALIASES, which must not rewrite allowed_options.
_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`` to send to LM Studio, or ``None`` = omit the
field (the user picked a level the model can't honor, so LM Studio falls back
to the model's declared default rather than a silently substituted effort).
Falsy ``allowed_options`` (probe failed) skips clamping."""
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 and effort not in {_LM_EFFORT_ALIASES.get(opt, opt) for opt in allowed_options}:
return None
return effort