Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ const ollamaSchema = baseProviderSettingsSchema.extend({
ollamaModelId: z.string().optional(),
ollamaBaseUrl: z.string().optional(),
ollamaApiKey: z.string().optional(),
ollamaNumCtx: z.number().int().min(128).optional(),
})

const vsCodeLmSchema = baseProviderSettingsSchema.extend({
Expand Down
98 changes: 98 additions & 0 deletions src/api/providers/__tests__/native-ollama.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,61 @@ describe("NativeOllamaHandler", () => {
expect(results[2]).toEqual({ type: "usage", inputTokens: 10, outputTokens: 2 })
})

it("should not include num_ctx by default", async () => {
// Mock the chat response
mockChat.mockImplementation(async function* () {
yield { message: { content: "Response" } }
})

const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])

// Consume the stream
for await (const _ of stream) {
// consume stream
}

// Verify that num_ctx was NOT included in the options
expect(mockChat).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.not.objectContaining({
num_ctx: expect.anything(),
}),
}),
)
})

it("should include num_ctx when explicitly set via ollamaNumCtx", async () => {
const options: ApiHandlerOptions = {
apiModelId: "llama2",
ollamaModelId: "llama2",
ollamaBaseUrl: "http://localhost:11434",
ollamaNumCtx: 8192, // Explicitly set num_ctx
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding tests for edge cases like negative numbers or extremely large values for ollamaNumCtx. Validation tests would help ensure the field only accepts reasonable values.

}

handler = new NativeOllamaHandler(options)

// Mock the chat response
mockChat.mockImplementation(async function* () {
yield { message: { content: "Response" } }
})

const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])

// Consume the stream
for await (const _ of stream) {
// consume stream
}

// Verify that num_ctx was included with the specified value
expect(mockChat).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
num_ctx: 8192,
}),
}),
)
})

it("should handle DeepSeek R1 models with reasoning detection", async () => {
const options: ApiHandlerOptions = {
apiModelId: "deepseek-r1",
Expand Down Expand Up @@ -120,6 +175,49 @@ describe("NativeOllamaHandler", () => {
})
expect(result).toBe("This is the response")
})

it("should not include num_ctx in completePrompt by default", async () => {
mockChat.mockResolvedValue({
message: { content: "Response" },
})

await handler.completePrompt("Test prompt")

// Verify that num_ctx was NOT included in the options
expect(mockChat).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.not.objectContaining({
num_ctx: expect.anything(),
}),
}),
)
})

it("should include num_ctx in completePrompt when explicitly set", async () => {
const options: ApiHandlerOptions = {
apiModelId: "llama2",
ollamaModelId: "llama2",
ollamaBaseUrl: "http://localhost:11434",
ollamaNumCtx: 4096, // Explicitly set num_ctx
}

handler = new NativeOllamaHandler(options)

mockChat.mockResolvedValue({
message: { content: "Response" },
})

await handler.completePrompt("Test prompt")

// Verify that num_ctx was included with the specified value
expect(mockChat).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
num_ctx: 4096,
}),
}),
)
})
})

describe("error handling", () => {
Expand Down
34 changes: 27 additions & 7 deletions src/api/providers/native-ollama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { getOllamaModels } from "./fetchers/ollama"
import { XmlMatcher } from "../../utils/xml-matcher"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"

interface OllamaChatOptions {
temperature: number
num_ctx?: number
}

function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
const ollamaMessages: Message[] = []

Expand Down Expand Up @@ -184,15 +189,22 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
)

try {
// Build options object conditionally
const chatOptions: OllamaChatOptions = {
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
}

// Only include num_ctx if explicitly set via ollamaNumCtx
if (this.options.ollamaNumCtx !== undefined) {
chatOptions.num_ctx = this.options.ollamaNumCtx
}

// Create the actual API request promise
const stream = await client.chat({
model: modelId,
messages: ollamaMessages,
stream: true,
options: {
num_ctx: modelInfo.contextWindow,
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
},
options: chatOptions,
})

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

// Build options object conditionally
const chatOptions: OllamaChatOptions = {
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
}

// Only include num_ctx if explicitly set via ollamaNumCtx
if (this.options.ollamaNumCtx !== undefined) {
chatOptions.num_ctx = this.options.ollamaNumCtx
}

const response = await client.chat({
model: modelId,
messages: [{ role: "user", content: prompt }],
stream: false,
options: {
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
},
options: chatOptions,
})

return response.message?.content || ""
Expand Down
6 changes: 6 additions & 0 deletions src/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider"> & {
* Defaults to true; set to false to disable summaries.
*/
enableGpt5ReasoningSummary?: boolean
/**
* Optional override for Ollama's num_ctx parameter.
* When set, this value will be used in Ollama chat requests.
* When undefined, Ollama will use the model's default num_ctx from the Modelfile.
*/
ollamaNumCtx?: number
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional that we're not adding this field to the ProviderSettings type in packages/types? Without that, the field won't be properly validated when settings are saved/loaded.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be added to the ProviderSettings?

}

// RouterName
Expand Down
20 changes: 20 additions & 0 deletions webview-ui/src/components/settings/providers/Ollama.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,26 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
))}
</VSCodeRadioGroup>
)}
<VSCodeTextField
value={apiConfiguration?.ollamaNumCtx?.toString() || ""}
onInput={(e: any) => {
const value = e.target?.value
if (value === "") {
setApiConfigurationField("ollamaNumCtx", undefined)
} else {
const numValue = parseInt(value, 10)
if (!isNaN(numValue) && numValue >= 128) {
setApiConfigurationField("ollamaNumCtx", numValue)
}
}
}}
placeholder="e.g., 4096"
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.ollama.numCtx")}</label>
<div className="text-xs text-vscode-descriptionForeground mt-1">
{t("settings:providers.ollama.numCtxHelp")}
</div>
</VSCodeTextField>
<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>
Expand Down
10 changes: 9 additions & 1 deletion webview-ui/src/components/ui/hooks/useSelectedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,17 @@ function getSelectedModel({
case "ollama": {
const id = apiConfiguration.ollamaModelId ?? ""
const info = ollamaModels && ollamaModels[apiConfiguration.ollamaModelId!]

const adjustedInfo =
info?.contextWindow &&
apiConfiguration?.ollamaNumCtx &&
apiConfiguration.ollamaNumCtx < info.contextWindow
? { ...info, contextWindow: apiConfiguration.ollamaNumCtx }
: info

return {
id,
info: info || undefined,
info: adjustedInfo || undefined,
}
}
case "lmstudio": {
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ca/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/de/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@
"modelId": "Model ID",
"apiKey": "Ollama API Key",
"apiKeyHelp": "Optional API key for authenticated Ollama instances or cloud services. Leave empty for local installations.",
"numCtx": "Context Window Size (num_ctx)",
"numCtxHelp": "Override the model's default context window size. Leave empty to use the model's Modelfile configuration. Minimum value is 128.",
"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."
},
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/es/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/fr/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/hi/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/id/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion webview-ui/src/i18n/locales/it/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ja/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ko/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/nl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/pl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/pt-BR/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading