Skip to content
Open
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
27 changes: 27 additions & 0 deletions core/autocomplete/templating/__tests__/renderPrompt.vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ vi.mock("../../../llm/countTokens", () => {
const pruneLinesFromBottom = (str: string, allowed: number) =>
str.slice(0, allowed);
const getTokenCountingBufferSafety = () => 0;
const MIN_RESPONSE_TOKENS = 1000;

return {
countTokens,
pruneLinesFromTop,
pruneLinesFromBottom,
getTokenCountingBufferSafety,
MIN_RESPONSE_TOKENS,
};
});

Expand Down Expand Up @@ -254,6 +256,31 @@ describe("renderPromptWithTokenLimit parity & pruning", () => {

expect(compiledPrefix.length).toBeLessThan(120);
});

it("does not wipe a small prompt when default maxTokens exceeds a small context length", () => {
// Regression test: a model whose contextLength (e.g. Ollama's num_ctx) is smaller
// than the default maxTokens reservation (4096) used to compute a negative prompt
// budget, which caused every prefix/suffix to be pruned to nothing regardless of
// how small the actual prompt was.
const shortPrefix = "A".repeat(50);

const helper = makeHelper({ prunedPrefix: shortPrefix });

const llmStub = {
contextLength: 2048,
completionOptions: {},
model: "test-model",
} as any;

const { prefix: compiledPrefix } = renderPromptWithTokenLimit({
snippetPayload: emptySnippetPayload,
workspaceDirs: ["file:///workspace"],
helper,
llm: llmStub,
});

expect(compiledPrefix.includes(shortPrefix)).toBe(true);
});
});

describe("stop-token merging", () => {
Expand Down
8 changes: 7 additions & 1 deletion core/autocomplete/templating/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DEFAULT_MAX_TOKENS } from "../../llm/constants.js";
import {
countTokens,
getTokenCountingBufferSafety,
MIN_RESPONSE_TOKENS,
pruneLinesFromBottom,
pruneLinesFromTop,
} from "../../llm/countTokens";
Expand Down Expand Up @@ -204,7 +205,12 @@ function pruneLength(llm: ILLM, prompt: string): number {
const contextLength = llm.contextLength;
const reservedTokens = llm.completionOptions.maxTokens ?? DEFAULT_MAX_TOKENS;
const safetyBuffer = getTokenCountingBufferSafety(contextLength);
const maxAllowedPromptTokens = contextLength - reservedTokens - safetyBuffer;
// Reserve at most MIN_RESPONSE_TOKENS for the completion, same as compileChatMessages.
// Otherwise a large maxTokens (the default is 4096) can consume the entire context
// window on small-context models, leaving no room for the prompt and silently
// pruning it to nothing on every request.
const minOutputTokens = Math.min(MIN_RESPONSE_TOKENS, reservedTokens);
const maxAllowedPromptTokens = contextLength - minOutputTokens - safetyBuffer;
const promptTokenCount = countTokens(prompt, llm.model);
return promptTokenCount - maxAllowedPromptTokens;
}
Expand Down
1 change: 1 addition & 0 deletions core/llm/countTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ export {
countTokens,
countTokensAsync,
extractToolSequence,
MIN_RESPONSE_TOKENS,
pruneLinesFromBottom,
pruneLinesFromTop,
pruneRawPromptFromTop,
Expand Down
Loading