Skip to content

Commit 3ffa090

Browse files
author
Kory Boyd
committed
feat(ollama): add reasoning model detection with config overrides
- Auto-detect reasoning models (qwen3, phi4, gemma3, llama3, deepseek, qwq, gpt-oss) - Add config support to force reasoning ON/OFF via capabilities.reasoning - Enable interleaved with reasoning_content field for reasoning models - Increase token limits (context: 200k, output: 32k) for reasoning models - Add default reasoningEffort: medium for reasoning models - Add think parameter support for Ollama API (true/false or low/medium/high for GPT-OSS)
1 parent 84cc792 commit 3ffa090

File tree

2 files changed

+64
-5
lines changed

2 files changed

+64
-5
lines changed

packages/opencode/src/provider/provider.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,23 @@ export namespace Provider {
10241024
mergeProvider(providerID, partial)
10251025
}
10261026

1027+
// Patterns to detect reasoning-capable Ollama models
1028+
const OLLAMA_REASONING_PATTERNS = [
1029+
/qwen[_\-]?3/i, // qwen3, qwen-3, qwen_3
1030+
/phi[_\-]?4/i, // phi4, phi-4
1031+
/gemma[_\-]?3/i, // gemma3, gemma-3
1032+
/llama[_\-]?3/i, // llama3, llama-3
1033+
/r1$/i, // deepseek-r1, etc.
1034+
/qwq/i, // QwQ
1035+
/deepseek/i, // DeepSeek family
1036+
/gpt-?oss/i, // GPT-OSS
1037+
]
1038+
1039+
function isOllamaReasoningModel(modelName: string, family?: string, families?: string[]): boolean {
1040+
const searchText = `${modelName} ${family ?? ""} ${families?.join(" ") ?? ""}`
1041+
return OLLAMA_REASONING_PATTERNS.some(pattern => pattern.test(searchText))
1042+
}
1043+
10271044
// Auto-detect Ollama if not already configured
10281045
const ollamaConfigured = providers["ollama"] || configProviders.some(([id]) => id === "ollama")
10291046
if (!ollamaConfigured) {
@@ -1035,6 +1052,19 @@ export namespace Provider {
10351052
for (const ollamaModel of ollama.models) {
10361053
const { model, tag } = parseModelName(ollamaModel.name)
10371054
const modelID = tag ? `${model}:${tag}` : model
1055+
1056+
// Detect if this is a reasoning model based on patterns
1057+
const isReasoning = isOllamaReasoningModel(
1058+
model,
1059+
ollamaModel.details?.family,
1060+
ollamaModel.details?.families,
1061+
)
1062+
1063+
// Check for config overrides - allow forcing reasoning on/off
1064+
const configModel = config.provider?.ollama?.models?.[modelID]
1065+
const configForceReasoning = configModel?.capabilities?.reasoning // undefined = auto, true = force on, false = force off
1066+
const finalReasoning = configForceReasoning !== undefined ? configForceReasoning : isReasoning
1067+
10381068
ollamaModels[modelID] = {
10391069
id: modelID,
10401070
providerID: ollamaProviderID,
@@ -1048,18 +1078,25 @@ export namespace Provider {
10481078
status: "active",
10491079
capabilities: {
10501080
temperature: true,
1051-
reasoning: ollamaModel.details?.family?.includes("reasoning") ?? false,
1081+
reasoning: finalReasoning,
10521082
attachment: false,
10531083
toolcall: true,
10541084
input: { text: true, audio: false, image: false, video: false, pdf: false },
10551085
output: { text: true, audio: false, image: false, video: false, pdf: false },
1056-
interleaved: false,
1086+
interleaved: finalReasoning
1087+
? configModel?.capabilities?.interleaved ?? { field: "reasoning_content" }
1088+
: false,
10571089
},
10581090
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
1059-
options: {},
1091+
options:
1092+
finalReasoning
1093+
? { reasoningEffort: configModel?.options?.reasoningEffort ?? "medium", ...configModel?.options }
1094+
: {},
10601095
limit: {
1061-
context: ollamaModel.details?.parameter_size ? 128000 : 8192,
1062-
output: 8192,
1096+
context:
1097+
configModel?.limit?.context ??
1098+
(finalReasoning ? 200000 : ollamaModel.details?.parameter_size ? 128000 : 8192),
1099+
output: configModel?.limit?.output ?? (finalReasoning ? 32768 : 8192),
10631100
},
10641101
headers: {},
10651102
release_date: "",

packages/opencode/src/provider/transform.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,28 @@ export namespace ProviderTransform {
756756
result["enable_thinking"] = true
757757
}
758758

759+
// Enable thinking for Ollama reasoning models via the "think" parameter
760+
// Ollama supports thinking for: DeepSeek R1, DeepSeek v3.1, Qwen 3, GPT-OSS
761+
// Most models accept true/false, GPT-OSS accepts low/medium/high
762+
if (
763+
input.model.providerID === "ollama" &&
764+
input.model.capabilities.reasoning &&
765+
input.model.api.npm === "@ai-sdk/openai-compatible"
766+
) {
767+
// Check if it's a GPT-OSS model (supports thinking levels)
768+
if (input.model.id.toLowerCase().includes("gpt-oss")) {
769+
result["think"] = input.model.options?.reasoningEffort ?? "medium"
770+
} else {
771+
// For most models, enable thinking when not disabled
772+
const effort = input.model.options?.reasoningEffort
773+
if (effort === "none") {
774+
result["think"] = false
775+
} else {
776+
result["think"] = true
777+
}
778+
}
779+
}
780+
759781
if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
760782
if (!input.model.api.id.includes("gpt-5-pro")) {
761783
result["reasoningEffort"] = "medium"

0 commit comments

Comments
 (0)