fix(agent): exempt parseable vLLM/LM Studio output-cap errors from compression-disabled guard

Salvage of #63862. is_output_cap_error() returns False for vLLM/LM Studio
error messages that contain 'prompt contains ... input tokens' (treated as
input-overflow signal). But parse_available_output_tokens_from_error() CAN
extract a valid available_tokens from those same messages. The
compression-disabled guard only checked is_output_cap_error(), so vLLM/LM
Studio users with compression off still got a terminal failure instead of
the max-tokens retry.

Fix: also exempt when parse_available_output_tokens_from_error() returns a
value — that function determines whether the retry path can actually handle
the error, so it's the right predicate for the exemption.

Added test: verify vLLM-format error with compression_disabled=False still
triggers the max-tokens retry path.

Co-authored-by: dmabry <dmabry@users.noreply.github.com>
This commit is contained in:
kshitijk4poor 2026-07-14 03:50:15 +05:30 committed by kshitij
parent af4006000f
commit 2ccfdb2db4
3 changed files with 55 additions and 1 deletions

View file

@ -3071,7 +3071,10 @@ def run_conversation(
FailoverReason.payload_too_large,
FailoverReason.context_overflow,
}
_is_output_cap_error = is_output_cap_error(error_msg)
_is_output_cap_error = (
is_output_cap_error(error_msg)
or parse_available_output_tokens_from_error(error_msg) is not None
)
if (
classified.reason in _overflow_reasons
and not getattr(agent, "compression_enabled", True)

View file

@ -138,6 +138,7 @@ AUTHOR_MAP = {
"259353979+testingbuddies24@users.noreply.github.com": "testingbuddies24", # PR #43192 salvage (strip orphan think-tag close tags in progressive gateway stream so a bare </think> whose open was dropped upstream can't leak to the user)
"shx_929@163.com": "Lazymonter", # PR #42914 salvage (retry launchd bootstrap after bootout on EIO for install/start instead of degrading to detached)
"96322396+WXBR@users.noreply.github.com": "WXBR", # PR #46183 salvage (persist recovered final_response at the finalize_turn chokepoint so recovery-path breaks don't drop the delivered assistant row)
"dmabry@sparky.fabe-gray.ts.net": "dmabry", # PR #63862 salvage (output-cap retry: use provider available_tokens + request estimate; exempt parseable vLLM/LM Studio errors from compression-disabled guard)
"sahil.rakhaiya117814@marwadiuniversity.ac.in": "SahilRakhaiya05", # PR #44073 salvage (fail-closed gateway/external-surface hardening: own-policy defaults, open-policy startup guard, profile-aware multiplex authz, API-server auth, execute_code per-session RPC token)
"5848605+itenev@users.noreply.github.com": "itenev", # PR #22753 salvage (asyncify model-context resolution in gateway message path so blocking requests.get can't starve Discord heartbeats)
"arthur.zhang@ingenico.com": "arthurzhang", # PR #34718 salvage (redact Slack App-Level xapp- tokens in agent/redact.py + gateway/run.py)

View file

@ -5580,6 +5580,56 @@ class TestRunConversation:
assert agent.context_compressor.context_length == 200_000
mock_compress.assert_not_called()
def test_output_cap_retry_with_compression_disabled_vllm_format(self, agent):
"""vLLM/LM Studio error messages contain 'prompt contains ... input
tokens' which is_output_cap_error() treats as an input-overflow signal
(returns False). But parse_available_output_tokens_from_error() CAN
extract a valid available_tokens from them. The compression-disabled
guard must exempt these too otherwise users on vLLM/LM Studio with
compression off get a terminal failure instead of a max-tokens retry.
"""
self._setup_agent(agent)
agent.api_mode = "chat_completions"
agent.provider = "openrouter"
agent.model = "some/model"
agent.max_tokens = 65_536
agent.compression_enabled = False
agent.context_compressor.context_length = 131_072
agent.context_compressor.should_compress = MagicMock(return_value=False)
# vLLM-format error (from tests/test_output_cap_parsing.py)
error_msg = (
"This model's maximum context length is 131072 tokens. "
"However, you requested 1024 output tokens and your prompt "
"contains at least 65537 input tokens, for a total of at least "
"66561 tokens."
)
exc = Exception(error_msg)
exc.status_code = 400
exc.code = 400
ok_resp = _mock_response(content="done", finish_reason="stop")
agent.client.chat.completions.create.side_effect = [exc, ok_resp]
mock_compress = MagicMock()
with (
patch.object(agent, "_persist_session"),
patch.object(agent, "_save_trajectory"),
patch.object(agent, "_cleanup_task_resources"),
patch.object(agent.context_compressor, "update_model"),
patch.object(agent, "_compress_context", mock_compress),
):
result = agent.run_conversation("hello")
assert len(agent.client.chat.completions.create.call_args_list) == 2
second_call = agent.client.chat.completions.create.call_args_list[1].kwargs
assert result["completed"] is True
assert result.get("compaction_disabled") is None
# parse_available_output_tokens_from_error returns 65535 for this message
assert second_call["max_tokens"] <= 65471 # 65535 - 64
assert agent.context_compressor.context_length == 131_072
mock_compress.assert_not_called()
class TestHookPayloadSanitizesSimpleNamespace:
"""Regression: ``_hook_jsonable`` referenced ``SimpleNamespace`` without