Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions agent/model_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
27 changes: 21 additions & 6 deletions tests/test_output_fit_preflight_clamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
Loading