mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 12:10:45 +03:00
fix(core): leave headroom when fitting output to the context window (#59934)
This commit is contained in:
@@ -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])),
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user