diff --git a/agent/model_metadata.py b/agent/model_metadata.py index 2ce157f11e9ac..b4bf55d529580 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -2659,7 +2659,17 @@ def estimate_messages_tokens_rough(messages: List[Dict[str, Any]]) -> int: # request, just a truncated answer the provider happily returns and the user # cannot use — the max_tokens=1 defect. ``None`` (leave the cap alone, let the # provider's own budget decide) is the honest answer there instead. -_OUTPUT_FIT_MIN_USABLE = 512 +# +# Deliberately the same value as ``conversation_loop. +# _LENGTH_CONTINUE_MIN_HEADROOM_TOKENS``: that guard refuses to scaffold another +# length-continuation round below it, on the grounds that there is no room left +# for a useful completion. Same question, so the same answer — and the two must +# not disagree. A clamp below that threshold would fight it: the truncated +# response starts a continuation round, the boost there sizes the next cap to +# the real context headroom, and this clamp shrinks it straight back to a value +# the continuation guard itself considers too small to write into. (No import — +# conversation_loop imports this module, not the other way round.) +_OUTPUT_FIT_MIN_USABLE = 2048 def output_tokens_that_fit( @@ -2697,15 +2707,16 @@ def output_tokens_that_fit( this local estimate makes it converge to a fitting value in one step. The reservation degrades in two tiers as the window fills (see the constants - above). The preferred 1.2x cushion is multiplicative, so it exhausts the - window on its own once the estimate passes ``(context_length - 2048) / 1.2`` - — ~82.5% fill. That is where the *cushion* becomes unaffordable, not where - the request stops fitting, so past that point the reservation drops to the - 1.15x safety contract and keeps reporting real caps (~9,000 tokens at 83% - fill of a 200,000 window, tapering to ``min_output``). ``None`` arrives - only when the contract itself admits nothing usable, at ~87% fill — the - ``1/1.15`` ceiling — and there the proactive clamp stands down and lets the - provider report its own authoritative budget on the reactive path. + above). The preferred 1.2x cushion is multiplicative, so it stops leaving a + usable cap once the estimate passes ``(context_length - 3584) / 1.2`` — + ~81.8% fill of a 200,000-token window. That is where the *cushion* becomes + unaffordable, not where the request stops fitting, so past that point the + reservation drops to the 1.15x safety contract and keeps reporting real caps + (~10,900 tokens at 82% fill, tapering to ``min_output``). ``None`` arrives + only when the contract itself admits nothing usable, at ~85.8% fill — where + the ``1/1.15`` ceiling meets the usable floor — and there the proactive + clamp stands down and lets the provider report its own authoritative budget + on the reactive path. """ if not isinstance(context_length, int) or context_length <= 0: return None diff --git a/tests/test_output_fit_preflight_clamp.py b/tests/test_output_fit_preflight_clamp.py index 249bc75391824..1ccb8500720f0 100644 --- a/tests/test_output_fit_preflight_clamp.py +++ b/tests/test_output_fit_preflight_clamp.py @@ -9,6 +9,7 @@ from types import SimpleNamespace from agent.chat_completion_helpers import _preflight_clamp_output_tokens +from agent.conversation_loop import _LENGTH_CONTINUE_MIN_HEADROOM_TOKENS from agent.model_metadata import ( _OUTPUT_FIT_MIN_USABLE, _OUTPUT_FIT_WINDOW_MARGIN, @@ -191,10 +192,23 @@ class TestHighFillBandDegradesGracefully: round-trip per turn. So the reservation now degrades to the 1.15x safety contract instead of - vanishing. ``None`` is still the answer past the ``1/1.15`` ceiling, where - nothing usable genuinely fits. + vanishing. ``None`` is still the answer past ~85.8% fill, where the + contract can no longer clear the usable floor. """ + def test_no_reported_cap_is_below_the_continuation_guard(self): + # ``_OUTPUT_FIT_MIN_USABLE`` is deliberately the same value as the + # length-continuation head-room guard, which refuses to scaffold another + # round below it because there is no room left for a useful completion. + # A clamp under that threshold would fight it: the truncated response + # starts a continuation round, the boost there sizes the next cap to the + # real context head-room, and the pre-flight clamp shrinks it straight + # back to a value the guard itself calls too small to write into. + assert _OUTPUT_FIT_MIN_USABLE >= _LENGTH_CONTINUE_MIN_HEADROOM_TOKENS + for tokens in range(1_000, _CTX_200K, 500): + fit = output_tokens_that_fit(_CTX_200K, _msgs(tokens)) + assert fit is None or fit >= _LENGTH_CONTINUE_MIN_HEADROOM_TOKENS + def test_reported_fit_where_the_preferred_cushion_admits_none(self): # The case from the report: at est=166,008 in a 200,000 window the 1.2x # reservation overruns the window outright, while the 1.15x contract @@ -207,10 +221,11 @@ def test_reported_fit_where_the_preferred_cushion_admits_none(self): assert _fits_for_a_denser_server(msgs, fit, _CTX_200K) def test_band_is_covered_without_a_hole(self): - # Sweep the whole band the old formula gave up on. Every fill level - # from the 1.2x limit (~82.7%) up to the 1.15x ceiling (~86.9%) must - # report a real cap, and the caps must taper rather than jump to None. - for pct in range(83, 87): + # Sweep the band the old formula gave up on. Every fill level from the + # 1.2x limit (~82.7%) up to where the contract stops clearing the usable + # floor (~85.8%) must report a real cap, and the caps must taper rather + # than jump to None. + for pct in range(83, 86): msgs = _msgs(int(_CTX_200K * pct / 100)) fit = output_tokens_that_fit(_CTX_200K, msgs) assert fit is not None, f"no cap reported at {pct}% fill"