mirror of
https://github.com/ollama/ollama.git
synced 2026-07-23 09:10:53 -05:00
launch: add thinking capability detection to opencode (#15434)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user