feat: add compact prompt mode for local LLMs

- Add compactPromptMode boolean setting to ProviderSettings type
- Implement compact prompt generation in SYSTEM_PROMPT function
- Add UI toggle in LM Studio and Ollama provider settings
- Create reusable CompactPromptControl component
- Add translation strings for the new feature
- Include comprehensive tests for compact prompt functionality

This feature addresses issue #7550 by providing a minimal prompt option
that reduces context size and improves response times for local LLMs
that have slower token generation speeds.
This commit is contained in:
Roo Code 2025-08-30 06:49:43 +00:00
parent c7d7ad8197
commit 576864bdff
8 changed files with 319 additions and 1 deletions

View file

@ -103,6 +103,7 @@ const baseProviderSettingsSchema = z.object({
modelTemperature: z.number().nullish(),
rateLimitSeconds: z.number().optional(),
consecutiveMistakeLimit: z.number().min(0).optional(),
compactPromptMode: z.boolean().optional(),
// Model reasoning.
enableReasoningEffort: z.boolean().optional(),

View file

@ -672,6 +672,194 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt).toContain("## update_todo_list")
})
describe("Compact Prompt Mode", () => {
it("should generate a compact prompt when compactPromptMode is true", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)
// Compact prompt should be significantly shorter
const normalPrompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
false, // compactPromptMode
)
// Compact prompt should be shorter
expect(prompt.length).toBeLessThan(normalPrompt.length)
// Should still contain essential sections
expect(prompt).toContain("You are Roo")
expect(prompt).toContain("## read_file")
expect(prompt).toContain("## write_to_file")
expect(prompt).toContain("## list_files")
expect(prompt).toContain("## search_files")
// Should NOT contain non-essential sections
expect(prompt).not.toContain("MCP")
expect(prompt).not.toContain("browser")
expect(prompt).not.toContain("CAPABILITIES")
expect(prompt).not.toContain("MODES")
expect(prompt).not.toContain("execute_command") // Execute command is not included in compact mode for architect mode
})
it("should generate a normal prompt when compactPromptMode is false", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
false, // compactPromptMode
)
// Normal prompt should contain all sections
expect(prompt).toContain("CAPABILITIES")
expect(prompt).toContain("MODES")
expect(prompt).toContain("RULES")
expect(prompt).toContain("SYSTEM INFORMATION")
expect(prompt).toContain("OBJECTIVE")
})
it("should generate a normal prompt when compactPromptMode is undefined", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
undefined, // compactPromptMode
)
// Should generate normal prompt by default
expect(prompt).toContain("CAPABILITIES")
expect(prompt).toContain("MODES")
expect(prompt).toContain("RULES")
expect(prompt).toContain("SYSTEM INFORMATION")
expect(prompt).toContain("OBJECTIVE")
})
it("should not include diff tool in compact mode even when diffEnabled is true", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
new MultiSearchReplaceDiffStrategy(), // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
true, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)
// Compact mode doesn't include diff tool to keep prompt minimal
expect(prompt).not.toContain("apply_diff")
})
it("should maintain custom instructions in compact mode", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
"Test global instructions", // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)
// Should still include custom instructions
expect(prompt).toContain("Test global instructions")
})
})
afterAll(() => {
vi.restoreAllMocks()
})

View file

@ -153,6 +153,7 @@ export const SYSTEM_PROMPT = async (
settings?: SystemPromptSettings,
todoList?: TodoItem[],
modelId?: string,
compactPromptMode?: boolean,
): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
@ -202,6 +203,62 @@ ${fileCustomSystemPrompt}
${customInstructions}`
}
// If compact prompt mode is enabled, generate a minimal prompt
if (compactPromptMode) {
const { roleDefinition, baseInstructions } = getModeSelection(mode, promptComponent, customModes)
const codeIndexManager = CodeIndexManager.getInstance(context, cwd)
// Generate a compact prompt with only essential sections
const compactPrompt = `${roleDefinition}
====
TOOL USE
You have access to tools that are executed upon user approval. Use one tool per message.
# Tools
${getToolDescriptionsForMode(
mode,
cwd,
false, // Disable computer use for compact mode
codeIndexManager,
undefined, // No diff strategy in compact mode
undefined, // No browser viewport
undefined, // No MCP in compact mode
customModes,
experiments,
partialReadsEnabled,
settings,
false, // No MCP server creation
modelId,
)}
====
RULES
- Project directory: ${cwd.toPosix()}
- Use tools efficiently to accomplish tasks
- Wait for user response after each tool use
- Be concise and direct in responses
====
OBJECTIVE
Complete the user's task efficiently using available tools.
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
language: language ?? formatLanguage(vscode.env.language),
rooIgnoreInstructions,
settings,
})}`
return compactPrompt
}
// If diff is disabled, don't pass the diffStrategy
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined

View file

@ -2242,6 +2242,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
apiConfiguration,
} = state ?? {}
// Check if we should use compact prompt mode for local LLM providers
const isLocalLLMProvider =
this.apiConfiguration.apiProvider === "lmstudio" || this.apiConfiguration.apiProvider === "ollama"
const shouldUseCompactPrompt = isLocalLLMProvider && this.apiConfiguration.compactPromptMode
return await (async () => {
const provider = this.providerRef.deref()
@ -2276,6 +2281,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
},
undefined, // todoList
this.api.getModel().id,
shouldUseCompactPrompt,
)
})()
}

View file

@ -0,0 +1,44 @@
import React from "react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
interface CompactPromptControlProps {
compactPromptMode?: boolean
onChange: (value: boolean) => void
providerName?: string
}
export const CompactPromptControl: React.FC<CompactPromptControlProps> = ({
compactPromptMode = false,
onChange,
providerName,
}) => {
const { t } = useAppTranslation()
// Determine the correct translation key prefix based on provider
const translationPrefix = providerName === "LM Studio" ? "providers.lmStudio" : "providers.ollama"
return (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<label htmlFor="compact-prompt-mode" className="font-medium">
{t(`settings:${translationPrefix}.compactPrompt.title`)}
</label>
<input
id="compact-prompt-mode"
type="checkbox"
checked={compactPromptMode}
onChange={(e) => onChange(e.target.checked)}
className="cursor-pointer"
/>
</div>
<p className="text-sm text-vscode-descriptionForeground">
{t(`settings:${translationPrefix}.compactPrompt.description`)}
</p>
{providerName && (
<p className="text-xs text-vscode-descriptionForeground italic">
{t(`settings:${translationPrefix}.compactPrompt.providerNote`, { provider: providerName })}
</p>
)}
</div>
)
}

View file

@ -13,6 +13,7 @@ import { vscode } from "@src/utils/vscode"
import { inputEventTransform } from "../transforms"
import { ModelRecord } from "@roo/api"
import { CompactPromptControl } from "../CompactPromptControl"
type LMStudioProps = {
apiConfiguration: ProviderSettings
@ -207,6 +208,11 @@ export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudi
)}
</>
)}
<CompactPromptControl
compactPromptMode={apiConfiguration?.compactPromptMode}
onChange={(value) => setApiConfigurationField("compactPromptMode", value)}
providerName="LM Studio"
/>
<div className="text-sm text-vscode-descriptionForeground">
<Trans
i18nKey="settings:providers.lmStudio.description"

View file

@ -11,6 +11,7 @@ import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
import { vscode } from "@src/utils/vscode"
import { inputEventTransform } from "../transforms"
import { CompactPromptControl } from "../CompactPromptControl"
type OllamaProps = {
apiConfiguration: ProviderSettings
@ -118,6 +119,11 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
))}
</VSCodeRadioGroup>
)}
<CompactPromptControl
compactPromptMode={apiConfiguration?.compactPromptMode}
onChange={(value) => setApiConfigurationField("compactPromptMode", value)}
providerName="Ollama"
/>
<div className="text-sm text-vscode-descriptionForeground">
{t("settings:providers.ollama.description")}
<span className="text-vscode-errorForeground ml-1">{t("settings:providers.ollama.warning")}</span>

View file

@ -368,7 +368,12 @@
"draftModelDesc": "Draft model must be from the same model family for speculative decoding to work correctly.",
"selectDraftModel": "Select Draft Model",
"noModelsFound": "No draft models found. Please ensure LM Studio is running with Server Mode enabled.",
"description": "LM Studio allows you to run models locally on your computer. For instructions on how to get started, see their <a>quickstart guide</a>. You will also need to start LM Studio's <b>local server</b> feature to use it with this extension. <span>Note:</span> Roo Code uses complex prompts and works best with Claude models. Less capable models may not work as expected."
"description": "LM Studio allows you to run models locally on your computer. For instructions on how to get started, see their <a>quickstart guide</a>. You will also need to start LM Studio's <b>local server</b> feature to use it with this extension. <span>Note:</span> Roo Code uses complex prompts and works best with Claude models. Less capable models may not work as expected.",
"compactPrompt": {
"title": "Compact Prompt Mode",
"description": "Reduces the system prompt size for faster response times with local LLMs. This removes non-essential sections while keeping core functionality.",
"providerNote": "Recommended for {{provider}} to prevent timeouts with slower local models"
}
},
"ollama": {
"baseUrl": "Base URL (optional)",
@ -376,6 +381,11 @@
"description": "Ollama allows you to run models locally on your computer. For instructions on how to get started, see their quickstart guide.",
"warning": "Note: Roo Code uses complex prompts and works best with Claude models. Less capable models may not work as expected."
},
"compactPrompt": {
"title": "Compact Prompt Mode",
"description": "Reduces the system prompt size for faster response times with local LLMs. This removes non-essential sections while keeping core functionality.",
"providerNote": "Recommended for {{provider}} to prevent timeouts with slower local models"
},
"unboundApiKey": "Unbound API Key",
"getUnboundApiKey": "Get Unbound API Key",
"unboundRefreshModelsSuccess": "Models list updated! You can now select from the latest models.",