launch: add thinking capability detection to opencode (#15434)

This commit is contained in:
Eva H
2026-06-18 10:45:16 -07:00
committed by GitHub
parent e1f7f9cbdb
commit 07ed752353
3 changed files with 109 additions and 19 deletions

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"runtime"
"slices"
"strings"
"github.com/ollama/ollama/cmd/internal/fileutil"
"github.com/ollama/ollama/envconfig"
@@ -278,6 +279,25 @@ func buildModelEntries(modelList []LaunchModel) map[string]any {
"output": []string{"text"},
}
}
if model.HasCapability("thinking") {
entry["reasoning"] = true
if openCodeModelSupportsThinkingLevels(model) {
entry["options"] = map[string]any{"reasoningEffort": "medium"}
entry["variants"] = map[string]any{
"low": map[string]any{"reasoningEffort": "low"},
"medium": map[string]any{"reasoningEffort": "medium"},
"high": map[string]any{"reasoningEffort": "high"},
"max": map[string]any{"reasoningEffort": "max"},
}
} else {
entry["variants"] = map[string]any{
"none": map[string]any{"reasoningEffort": "none"},
"low": map[string]any{"disabled": true},
"medium": map[string]any{"disabled": true},
"high": map[string]any{"disabled": true},
}
}
}
if model.MaxOutputTokens > 0 {
limit := make(map[string]any)
if model.ContextLength > 0 {
@@ -290,3 +310,20 @@ func buildModelEntries(modelList []LaunchModel) map[string]any {
}
return models
}
func openCodeModelSupportsThinkingLevels(model LaunchModel) bool {
for _, family := range append([]string{model.Details.Family}, model.Details.Families...) {
if normalizeOpenCodeModelFamily(family) == "gptoss" {
return true
}
}
return strings.Contains(normalizeOpenCodeModelFamily(model.Name), "gptoss")
}
func normalizeOpenCodeModelFamily(s string) string {
s = strings.ToLower(s)
s = strings.ReplaceAll(s, "-", "")
s = strings.ReplaceAll(s, "_", "")
return s
}

View File

@@ -8,6 +8,7 @@ import (
"runtime"
"testing"
"github.com/ollama/ollama/api"
"github.com/ollama/ollama/types/model"
)
@@ -174,6 +175,54 @@ func TestOpenCodeEdit(t *testing.T) {
t.Fatalf("modalities.output = %v, want [text]", output)
}
})
t.Run("thinking model gets on off reasoning variants", func(t *testing.T) {
models := buildModelEntries([]LaunchModel{{Name: "thinking-model", Capabilities: []model.Capability{model.CapabilityThinking}}})
entry, _ := models["thinking-model"].(map[string]any)
if entry["reasoning"] != true {
t.Fatalf("reasoning = %v, want true", entry["reasoning"])
}
variants, _ := entry["variants"].(map[string]any)
none, _ := variants["none"].(map[string]any)
if none["reasoningEffort"] != "none" {
t.Fatalf("variants.none.reasoningEffort = %v, want none", none["reasoningEffort"])
}
for _, level := range []string{"low", "medium", "high"} {
variant, _ := variants[level].(map[string]any)
if variant["disabled"] != true {
t.Fatalf("variants.%s.disabled = %v, want true", level, variant["disabled"])
}
}
})
t.Run("gpt oss gets reasoning level variants", func(t *testing.T) {
models := buildModelEntries([]LaunchModel{{Name: "gpt-oss:120b-cloud", Capabilities: []model.Capability{model.CapabilityThinking}}})
entry, _ := models["gpt-oss:120b-cloud"].(map[string]any)
options, _ := entry["options"].(map[string]any)
if options["reasoningEffort"] != "medium" {
t.Fatalf("options.reasoningEffort = %v, want medium", options["reasoningEffort"])
}
variants, _ := entry["variants"].(map[string]any)
for _, level := range []string{"low", "medium", "high", "max"} {
variant, _ := variants[level].(map[string]any)
if variant["reasoningEffort"] != level {
t.Fatalf("variants.%s.reasoningEffort = %v, want %s", level, variant["reasoningEffort"], level)
}
}
})
t.Run("gpt oss family gets reasoning level variants", func(t *testing.T) {
models := buildModelEntries([]LaunchModel{{Name: "reasoning-model", Capabilities: []model.Capability{model.CapabilityThinking}, Details: api.ModelDetails{Families: []string{"gptoss"}}}})
entry, _ := models["reasoning-model"].(map[string]any)
variants, _ := entry["variants"].(map[string]any)
max, _ := variants["max"].(map[string]any)
if max["reasoningEffort"] != "max" {
t.Fatalf("variants.max.reasoningEffort = %v, want max", max["reasoningEffort"])
}
})
}
func TestBuildModelEntries(t *testing.T) {

View File

@@ -986,15 +986,35 @@ func TestLlamaServerWaitUntilRunningTimesOutWhenLoadStalls(t *testing.T) {
}
func TestLlamaServerWaitUntilRunningExtendsTimeoutOnOutputActivity(t *testing.T) {
t.Setenv("OLLAMA_LOAD_TIMEOUT", "20ms")
t.Setenv("OLLAMA_LOAD_TIMEOUT", "100ms")
var activityCount atomic.Int32
var activityStarted atomic.Bool
var runner *llamaServerRunner
done := make(chan struct{})
defer close(done)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/health" {
t.Errorf("unexpected path: %s", r.URL.Path)
return
}
if activityCount.Load() < 5 {
if !activityStarted.Swap(true) {
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-ticker.C:
activityCount.Add(1)
_, _ = runner.output.Write([]byte("."))
}
}
}()
}
if activityCount.Load() < 3 {
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprint(w, `{"error":{"message":"Loading model","type":"unavailable_error","code":503}}`)
return
@@ -1007,28 +1027,12 @@ func TestLlamaServerWaitUntilRunningExtendsTimeoutOnOutputActivity(t *testing.T)
var portInt int
fmt.Sscanf(parts[len(parts)-1], "%d", &portInt)
runner := &llamaServerRunner{
runner = &llamaServerRunner{
port: portInt,
cmd: fakeRunningCmd(),
}
runner.output = &memoryParsingWriter{inner: io.Discard, runner: runner}
done := make(chan struct{})
defer close(done)
go func() {
ticker := time.NewTicker(5 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-ticker.C:
activityCount.Add(1)
_, _ = runner.output.Write([]byte("."))
}
}
}()
if err := runner.WaitUntilRunning(t.Context()); err != nil {
t.Fatalf("WaitUntilRunning error: %v", err)
}