Skip to content

Commit 12f94fc

Browse files
roomote[bot]roomotehannesrudolphdaniel-lxs
authored
fix: respect Ollama Modelfile num_ctx configuration (#7798)
Co-authored-by: Roo Code <[email protected]> Co-authored-by: Hannes Rudolph <[email protected]> Co-authored-by: daniel-lxs <[email protected]>
1 parent 60ba541 commit 12f94fc

File tree

24 files changed

+198
-9
lines changed

24 files changed

+198
-9
lines changed

packages/types/src/provider-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ const ollamaSchema = baseProviderSettingsSchema.extend({
258258
ollamaModelId: z.string().optional(),
259259
ollamaBaseUrl: z.string().optional(),
260260
ollamaApiKey: z.string().optional(),
261+
ollamaNumCtx: z.number().int().min(128).optional(),
261262
})
262263

263264
const vsCodeLmSchema = baseProviderSettingsSchema.extend({

src/api/providers/__tests__/native-ollama.spec.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,61 @@ describe("NativeOllamaHandler", () => {
7373
expect(results[2]).toEqual({ type: "usage", inputTokens: 10, outputTokens: 2 })
7474
})
7575

76+
it("should not include num_ctx by default", async () => {
77+
// Mock the chat response
78+
mockChat.mockImplementation(async function* () {
79+
yield { message: { content: "Response" } }
80+
})
81+
82+
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
83+
84+
// Consume the stream
85+
for await (const _ of stream) {
86+
// consume stream
87+
}
88+
89+
// Verify that num_ctx was NOT included in the options
90+
expect(mockChat).toHaveBeenCalledWith(
91+
expect.objectContaining({
92+
options: expect.not.objectContaining({
93+
num_ctx: expect.anything(),
94+
}),
95+
}),
96+
)
97+
})
98+
99+
it("should include num_ctx when explicitly set via ollamaNumCtx", async () => {
100+
const options: ApiHandlerOptions = {
101+
apiModelId: "llama2",
102+
ollamaModelId: "llama2",
103+
ollamaBaseUrl: "http://localhost:11434",
104+
ollamaNumCtx: 8192, // Explicitly set num_ctx
105+
}
106+
107+
handler = new NativeOllamaHandler(options)
108+
109+
// Mock the chat response
110+
mockChat.mockImplementation(async function* () {
111+
yield { message: { content: "Response" } }
112+
})
113+
114+
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
115+
116+
// Consume the stream
117+
for await (const _ of stream) {
118+
// consume stream
119+
}
120+
121+
// Verify that num_ctx was included with the specified value
122+
expect(mockChat).toHaveBeenCalledWith(
123+
expect.objectContaining({
124+
options: expect.objectContaining({
125+
num_ctx: 8192,
126+
}),
127+
}),
128+
)
129+
})
130+
76131
it("should handle DeepSeek R1 models with reasoning detection", async () => {
77132
const options: ApiHandlerOptions = {
78133
apiModelId: "deepseek-r1",
@@ -120,6 +175,49 @@ describe("NativeOllamaHandler", () => {
120175
})
121176
expect(result).toBe("This is the response")
122177
})
178+
179+
it("should not include num_ctx in completePrompt by default", async () => {
180+
mockChat.mockResolvedValue({
181+
message: { content: "Response" },
182+
})
183+
184+
await handler.completePrompt("Test prompt")
185+
186+
// Verify that num_ctx was NOT included in the options
187+
expect(mockChat).toHaveBeenCalledWith(
188+
expect.objectContaining({
189+
options: expect.not.objectContaining({
190+
num_ctx: expect.anything(),
191+
}),
192+
}),
193+
)
194+
})
195+
196+
it("should include num_ctx in completePrompt when explicitly set", async () => {
197+
const options: ApiHandlerOptions = {
198+
apiModelId: "llama2",
199+
ollamaModelId: "llama2",
200+
ollamaBaseUrl: "http://localhost:11434",
201+
ollamaNumCtx: 4096, // Explicitly set num_ctx
202+
}
203+
204+
handler = new NativeOllamaHandler(options)
205+
206+
mockChat.mockResolvedValue({
207+
message: { content: "Response" },
208+
})
209+
210+
await handler.completePrompt("Test prompt")
211+
212+
// Verify that num_ctx was included with the specified value
213+
expect(mockChat).toHaveBeenCalledWith(
214+
expect.objectContaining({
215+
options: expect.objectContaining({
216+
num_ctx: 4096,
217+
}),
218+
}),
219+
)
220+
})
123221
})
124222

125223
describe("error handling", () => {

src/api/providers/native-ollama.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { getOllamaModels } from "./fetchers/ollama"
88
import { XmlMatcher } from "../../utils/xml-matcher"
99
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
1010

11+
interface OllamaChatOptions {
12+
temperature: number
13+
num_ctx?: number
14+
}
15+
1116
function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
1217
const ollamaMessages: Message[] = []
1318

@@ -184,15 +189,22 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
184189
)
185190

186191
try {
192+
// Build options object conditionally
193+
const chatOptions: OllamaChatOptions = {
194+
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
195+
}
196+
197+
// Only include num_ctx if explicitly set via ollamaNumCtx
198+
if (this.options.ollamaNumCtx !== undefined) {
199+
chatOptions.num_ctx = this.options.ollamaNumCtx
200+
}
201+
187202
// Create the actual API request promise
188203
const stream = await client.chat({
189204
model: modelId,
190205
messages: ollamaMessages,
191206
stream: true,
192-
options: {
193-
num_ctx: modelInfo.contextWindow,
194-
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
195-
},
207+
options: chatOptions,
196208
})
197209

198210
let totalInputTokens = 0
@@ -274,13 +286,21 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
274286
const { id: modelId } = await this.fetchModel()
275287
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
276288

289+
// Build options object conditionally
290+
const chatOptions: OllamaChatOptions = {
291+
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
292+
}
293+
294+
// Only include num_ctx if explicitly set via ollamaNumCtx
295+
if (this.options.ollamaNumCtx !== undefined) {
296+
chatOptions.num_ctx = this.options.ollamaNumCtx
297+
}
298+
277299
const response = await client.chat({
278300
model: modelId,
279301
messages: [{ role: "user", content: prompt }],
280302
stream: false,
281-
options: {
282-
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
283-
},
303+
options: chatOptions,
284304
})
285305

286306
return response.message?.content || ""

src/shared/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider"> & {
1818
* Defaults to true; set to false to disable summaries.
1919
*/
2020
enableGpt5ReasoningSummary?: boolean
21+
/**
22+
* Optional override for Ollama's num_ctx parameter.
23+
* When set, this value will be used in Ollama chat requests.
24+
* When undefined, Ollama will use the model's default num_ctx from the Modelfile.
25+
*/
26+
ollamaNumCtx?: number
2127
}
2228

2329
// RouterName

webview-ui/src/components/settings/providers/Ollama.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,26 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
130130
))}
131131
</VSCodeRadioGroup>
132132
)}
133+
<VSCodeTextField
134+
value={apiConfiguration?.ollamaNumCtx?.toString() || ""}
135+
onInput={(e: any) => {
136+
const value = e.target?.value
137+
if (value === "") {
138+
setApiConfigurationField("ollamaNumCtx", undefined)
139+
} else {
140+
const numValue = parseInt(value, 10)
141+
if (!isNaN(numValue) && numValue >= 128) {
142+
setApiConfigurationField("ollamaNumCtx", numValue)
143+
}
144+
}
145+
}}
146+
placeholder="e.g., 4096"
147+
className="w-full">
148+
<label className="block font-medium mb-1">{t("settings:providers.ollama.numCtx")}</label>
149+
<div className="text-xs text-vscode-descriptionForeground mt-1">
150+
{t("settings:providers.ollama.numCtxHelp")}
151+
</div>
152+
</VSCodeTextField>
133153
<div className="text-sm text-vscode-descriptionForeground">
134154
{t("settings:providers.ollama.description")}
135155
<span className="text-vscode-errorForeground ml-1">{t("settings:providers.ollama.warning")}</span>

webview-ui/src/components/ui/hooks/useSelectedModel.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,17 @@ function getSelectedModel({
263263
case "ollama": {
264264
const id = apiConfiguration.ollamaModelId ?? ""
265265
const info = ollamaModels && ollamaModels[apiConfiguration.ollamaModelId!]
266+
267+
const adjustedInfo =
268+
info?.contextWindow &&
269+
apiConfiguration?.ollamaNumCtx &&
270+
apiConfiguration.ollamaNumCtx < info.contextWindow
271+
? { ...info, contextWindow: apiConfiguration.ollamaNumCtx }
272+
: info
273+
266274
return {
267275
id,
268-
info: info || undefined,
276+
info: adjustedInfo || undefined,
269277
}
270278
}
271279
case "lmstudio": {

webview-ui/src/i18n/locales/ca/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@
380380
"modelId": "Model ID",
381381
"apiKey": "Ollama API Key",
382382
"apiKeyHelp": "Optional API key for authenticated Ollama instances or cloud services. Leave empty for local installations.",
383+
"numCtx": "Context Window Size (num_ctx)",
384+
"numCtxHelp": "Override the model's default context window size. Leave empty to use the model's Modelfile configuration. Minimum value is 128.",
383385
"description": "Ollama allows you to run models locally on your computer. For instructions on how to get started, see their quickstart guide.",
384386
"warning": "Note: Roo Code uses complex prompts and works best with Claude models. Less capable models may not work as expected."
385387
},

webview-ui/src/i18n/locales/es/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)