fix: honor configured max_turns in WebUI agents
Read agent.max_turns when constructing streaming WebUI AIAgent instances, pass it as max_iterations when supported, and include it in the per-session agent cache signature so budget changes take effect. Add regression coverage for the config read, constructor kwarg, and cache key.
This commit is contained in:
1 parent
773857d159
commit
01b9c82dc9
2 files changed
+56
No files matched your search
@@ -2296,6 +2296,29 @@ def _run_agent_streaming(
|
|||||||
import inspect as _inspect
|
import inspect as _inspect
|
||||||
_agent_params = set(_inspect.signature(_AIAgent.__init__).parameters)
|
_agent_params = set(_inspect.signature(_AIAgent.__init__).parameters)
|
||||||
|
|
||||||
|
# CLI-parity max-iteration budget: read config.yaml's
|
||||||
|
# agent.max_turns and pass it to AIAgent when supported. Without
|
||||||
|
# this WebUI-created agents silently use AIAgent's constructor
|
||||||
|
# default (90), so long browser-originated tasks hit the
|
||||||
|
# "maximum number of tool-calling iterations" summary path even
|
||||||
|
# after the operator raises Hermes' global turn budget.
|
||||||
|
_max_iterations_cfg = None
|
||||||
|
try:
|
||||||
|
_raw_max_iterations = None
|
||||||
|
_agent_cfg_for_iterations = _cfg.get('agent', {}) if isinstance(_cfg, dict) else {}
|
||||||
|
if isinstance(_agent_cfg_for_iterations, dict):
|
||||||
|
_raw_max_iterations = _agent_cfg_for_iterations.get('max_turns')
|
||||||
|
if _raw_max_iterations is None and isinstance(_cfg, dict):
|
||||||
|
# Back-compat for older Hermes config files that used a
|
||||||
|
# root-level max_turns key.
|
||||||
|
_raw_max_iterations = _cfg.get('max_turns')
|
||||||
|
if _raw_max_iterations is not None:
|
||||||
|
_parsed_max_iterations = int(_raw_max_iterations)
|
||||||
|
if _parsed_max_iterations > 0:
|
||||||
|
_max_iterations_cfg = _parsed_max_iterations
|
||||||
|
except Exception:
|
||||||
|
_max_iterations_cfg = None
|
||||||
|
|
||||||
# CLI-parity max output cap: read config.yaml's max_tokens and pass
|
# CLI-parity max output cap: read config.yaml's max_tokens and pass
|
||||||
# it to AIAgent when supported. Without this WebUI-created agents use
|
# it to AIAgent when supported. Without this WebUI-created agents use
|
||||||
# provider-native output ceilings (e.g. Claude via OpenRouter can
|
# provider-native output ceilings (e.g. Claude via OpenRouter can
|
||||||
@@ -2355,6 +2378,8 @@ def _run_agent_streaming(
|
|||||||
_agent_kwargs['reasoning_config'] = _reasoning_config
|
_agent_kwargs['reasoning_config'] = _reasoning_config
|
||||||
if 'status_callback' in _agent_params:
|
if 'status_callback' in _agent_params:
|
||||||
_agent_kwargs['status_callback'] = _agent_status_callback
|
_agent_kwargs['status_callback'] = _agent_status_callback
|
||||||
|
if 'max_iterations' in _agent_params and _max_iterations_cfg is not None:
|
||||||
|
_agent_kwargs['max_iterations'] = _max_iterations_cfg
|
||||||
if 'max_tokens' in _agent_params and _max_tokens_cfg is not None:
|
if 'max_tokens' in _agent_params and _max_tokens_cfg is not None:
|
||||||
_agent_kwargs['max_tokens'] = _max_tokens_cfg
|
_agent_kwargs['max_tokens'] = _max_tokens_cfg
|
||||||
# Params added in newer hermes-agent — skip if not supported
|
# Params added in newer hermes-agent — skip if not supported
|
||||||
@@ -2388,6 +2413,7 @@ def _run_agent_streaming(
|
|||||||
_hashlib.sha256((resolved_api_key or '').encode()).hexdigest()[:16],
|
_hashlib.sha256((resolved_api_key or '').encode()).hexdigest()[:16],
|
||||||
resolved_base_url or '',
|
resolved_base_url or '',
|
||||||
resolved_provider or '',
|
resolved_provider or '',
|
||||||
|
_max_iterations_cfg or '',
|
||||||
_max_tokens_cfg or '',
|
_max_tokens_cfg or '',
|
||||||
_fallback_resolved or {},
|
_fallback_resolved or {},
|
||||||
sorted(_toolsets) if _toolsets else [],
|
sorted(_toolsets) if _toolsets else [],
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Regression checks for WebUI AIAgent iteration-budget parity.
|
||||||
|
|
||||||
|
WebUI streaming agents must honor Hermes' configured agent.max_turns. Otherwise
|
||||||
|
browser-originated long-running tasks silently fall back to AIAgent's constructor
|
||||||
|
default and hit the "maximum number of tool-calling iterations" summary path even
|
||||||
|
when the operator raised the global Hermes budget.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
STREAMING_PY = (REPO / "api" / "streaming.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_agent_reads_agent_max_turns_from_config():
|
||||||
|
assert "_agent_cfg_for_iterations" in STREAMING_PY
|
||||||
|
assert "_agent_cfg_for_iterations.get('max_turns')" in STREAMING_PY
|
||||||
|
assert "_cfg.get('max_turns')" in STREAMING_PY
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_agent_passes_max_iterations_to_aiagent():
|
||||||
|
assert "if 'max_iterations' in _agent_params and _max_iterations_cfg is not None:" in STREAMING_PY
|
||||||
|
assert "_agent_kwargs['max_iterations'] = _max_iterations_cfg" in STREAMING_PY
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_agent_cache_signature_includes_max_iterations():
|
||||||
|
sig_start = STREAMING_PY.index("_sig_blob = _json.dumps")
|
||||||
|
sig_block = STREAMING_PY[sig_start:STREAMING_PY.index("], sort_keys=True)", sig_start)]
|
||||||
|
assert "_max_iterations_cfg or ''" in sig_block
|
||||||
Reference in new issue
Block a user