fix(core): leave headroom when fitting output to the context window (#59934)

This commit is contained in:
Filip
2026-10-28 20:37:46 +01:00
committed by GitHub
Unverified
parent a882cf98a0
commit da702a85a6
2 changed files with 17 additions and 7 deletions
+14 -5
View File
@@ -50,6 +50,10 @@ const OUTPUT_TOKEN_FALLBACK = 32_000
const OUTPUT_TOKEN_MAX = 256_000
// Prompt text is estimated at about 4 characters per token, which can run low on dense text such as code.
const ESTIMATE_ERROR = 0.15
// Share of the window a turn leaves unused when its output limit is fitted to the room left. Providers count a few
// tokens more than we do (chat template, tool schemas), and strict ones reject input plus output past the window.
// Compaction sizes its summary against its own reserve instead.
const WINDOW_HEADROOM = 0.01
// Never ask for less; only reachable with automatic compaction off, since it keeps the window from filling this far.
const OUTPUT_TOKEN_MIN = 1_024
@@ -82,10 +86,13 @@ export interface Input {
}
/** The default output limit: the catalog limit, fitted to the room the prompt leaves in the context window. */
export const outputLimit = (limit: Model.Info["limit"], inputTokens?: Input["inputTokens"]) => {
export const outputLimit = (limit: Model.Info["limit"], inputTokens?: Input["inputTokens"], headroom = 0) => {
const requested = Math.min(limit.output > 0 ? limit.output : OUTPUT_TOKEN_FALLBACK, OUTPUT_TOKEN_MAX)
if (inputTokens === undefined || limit.context <= 0) return requested
const room = limit.context - inputTokens.measured - Math.ceil(inputTokens.estimated * (1 + ESTIMATE_ERROR))
const room =
Math.floor(limit.context * (1 - headroom)) -
inputTokens.measured -
Math.ceil(inputTokens.estimated * (1 + ESTIMATE_ERROR))
return Math.min(requested, Math.max(OUTPUT_TOKEN_MIN, room))
}
@@ -255,9 +262,11 @@ export const layer = Layer.effect(
system: input.system,
messages: input.messages,
options:
kind === "primary" || kind === "compaction"
? { maxTokens: outputLimit(model.limit, input.inputTokens) }
: {},
kind === "primary"
? { maxTokens: outputLimit(model.limit, input.inputTokens, WINDOW_HEADROOM) }
: kind === "compaction"
? { maxTokens: outputLimit(model.limit, input.inputTokens) }
: {},
},
Object.fromEntries(Array.from(given, ([d, t]) => [t.name, d])),
)
+3 -2
View File
@@ -3561,8 +3561,9 @@ describe("SessionRunnerLLM", () => {
yield* s.runPrompt("Continue")
expect(s.requests[0]?.generation?.maxTokens).toBe(64_000)
expect(s.requests[1]?.generation?.maxTokens).toBeLessThan(100_000 - 50_000)
expect(s.requests[1]?.generation?.maxTokens).toBeGreaterThan(100_000 - 50_000 - 200)
// 1% of the window stays unused, since strict providers reject a request that overshoots it by a few tokens.
expect(s.requests[1]?.generation?.maxTokens).toBeLessThan(99_000 - 50_000)
expect(s.requests[1]?.generation?.maxTokens).toBeGreaterThan(99_000 - 50_000 - 200)
})
scenario("caps the output limit a large model advertises", function* (s) {