mirror of
https://github.com/ollama/ollama.git
synced 2026-09-21 05:28:00 -05:00
176 lines
5.8 KiB
Go
176 lines
5.8 KiB
Go
package openai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/ollama/ollama/api"
|
|
"github.com/ollama/ollama/types/model"
|
|
)
|
|
|
|
func TestThinkingConversionMetadata(t *testing.T) {
|
|
for _, metadata := range []struct {
|
|
name string
|
|
thinking *model.Thinking
|
|
generic bool
|
|
}{
|
|
{"named", &model.Thinking{Values: []any{false, "high", "max"}, Default: "high"}, true},
|
|
{"nonthinking", &model.Thinking{Values: []any{false}, Default: false}, true},
|
|
{"nil", nil, false},
|
|
{"invalid", &model.Thinking{Values: []any{"high"}, Default: "missing"}, false},
|
|
} {
|
|
for _, protocol := range []string{"chat", "responses"} {
|
|
for _, tt := range []struct {
|
|
name, fields string
|
|
wantGeneric, wantLegacy any
|
|
legacyError bool
|
|
}{
|
|
{"omitted", ``, nil, nil, false},
|
|
{"empty", `,"reasoning":{"effort":""}`, nil, nil, false},
|
|
{"none", `,"reasoning":{"effort":"none"}`, false, false, false},
|
|
{"supported", `,"reasoning":{"effort":"max"}`, "max", "max", false},
|
|
{"unsupported", `,"reasoning":{"effort":"low"}`, "low", "low", false},
|
|
{"xhigh", `,"reasoning":{"effort":"xhigh"}`, "xhigh", "max", false},
|
|
{"minimal", `,"reasoning":{"effort":"minimal"}`, "minimal", "low", false},
|
|
{"future", `,"reasoning":{"effort":"future"}`, "future", nil, true},
|
|
{"exact spelling", `,"reasoning":{"effort":" HIGH "}`, " HIGH ", nil, true},
|
|
{"nested precedence", `,"reasoning_effort":"low","reasoning":{"effort":"xhigh"}`, "xhigh", "max", false},
|
|
{"empty nested precedence", `,"reasoning_effort":"low","reasoning":{"effort":""}`, nil, nil, false},
|
|
} {
|
|
t.Run(metadata.name+"/"+protocol+"/"+tt.name, func(t *testing.T) {
|
|
body := []byte(`{"model":"test","messages":[{"role":"user","content":"hi"}],"input":"hi"` + tt.fields + `}`)
|
|
var request any
|
|
if protocol == "chat" {
|
|
request = &ChatCompletionRequest{}
|
|
} else {
|
|
request = &ResponsesRequest{}
|
|
}
|
|
if err := json.Unmarshal(body, request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before, err := json.Marshal(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var result *api.ChatRequest
|
|
switch req := request.(type) {
|
|
case *ChatCompletionRequest:
|
|
result, err = FromChatRequest(*req, metadata.thinking)
|
|
case *ResponsesRequest:
|
|
result, err = FromResponsesRequest(*req, metadata.thinking)
|
|
}
|
|
after, marshalErr := json.Marshal(request)
|
|
if marshalErr != nil || string(before) != string(after) {
|
|
t.Fatalf("conversion mutated input: before=%s after=%s error=%v", before, after, marshalErr)
|
|
}
|
|
if !metadata.generic && tt.legacyError {
|
|
if err == nil {
|
|
t.Fatal("expected legacy effort error")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := tt.wantLegacy
|
|
if metadata.generic {
|
|
want = tt.wantGeneric
|
|
}
|
|
var got any
|
|
if result.Think != nil {
|
|
got = result.Think.Value
|
|
}
|
|
if got != want {
|
|
t.Fatalf("thinking=%#v, want %#v", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResponsesThinkingOverrideWithMetadata(t *testing.T) {
|
|
for _, metadata := range []struct {
|
|
name string
|
|
thinking *model.Thinking
|
|
}{
|
|
{"named", &model.Thinking{Values: []any{false, "high", "max"}, Default: "high"}},
|
|
{"nil", nil},
|
|
{"invalid", &model.Thinking{Values: []any{"high"}, Default: "missing"}},
|
|
} {
|
|
for _, value := range []any{false, true, "low", "high", "max", "", "xhigh", "minimal", "future", 75} {
|
|
t.Run(metadata.name+"/"+fmt.Sprint(value), func(t *testing.T) {
|
|
req := ResponsesRequest{Input: ResponsesInput{Text: "hi"}, Think: &api.ThinkValue{Value: value}}
|
|
req.Reasoning.Effort = "xhigh"
|
|
got, err := FromResponsesRequest(req, metadata.thinking)
|
|
wantErr := value == 75 || (!metadata.thinking.Valid() && (value == "" || value == "xhigh" || value == "minimal" || value == "future"))
|
|
if wantErr {
|
|
if err == nil {
|
|
t.Fatal("invalid override must fail")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Think == nil || got.Think.Value != value {
|
|
t.Fatalf("thinking=%v, want explicit override %#v", got.Think, value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThinkingBooleanOpenAIControls(t *testing.T) {
|
|
for _, metadata := range []struct {
|
|
name string
|
|
thinking *model.Thinking
|
|
boolean bool
|
|
}{
|
|
{"default off", &model.Thinking{Values: []any{false, true}, Default: false}, true},
|
|
{"default on", &model.Thinking{Values: []any{false, true}, Default: true}, true},
|
|
{"always on", &model.Thinking{Values: []any{true}, Default: true}, true},
|
|
{"off only", &model.Thinking{Values: []any{false}, Default: false}, false},
|
|
{"mixed", &model.Thinking{Values: []any{false, true, "medium"}, Default: true}, false},
|
|
} {
|
|
for _, protocol := range []string{"chat", "responses"} {
|
|
for _, effort := range []string{"", "none", "low", "medium", "high", "max", "minimal", "xhigh", "ultra", "future", " HIGH "} {
|
|
t.Run(metadata.name+"/"+protocol+"/"+effort, func(t *testing.T) {
|
|
var got *api.ChatRequest
|
|
var err error
|
|
if protocol == "chat" {
|
|
got, err = FromChatRequest(ChatCompletionRequest{Model: "test", ReasoningEffort: &effort}, metadata.thinking)
|
|
} else {
|
|
req := ResponsesRequest{Model: "test", Input: ResponsesInput{Text: "hi"}}
|
|
req.Reasoning.Effort = effort
|
|
got, err = FromResponsesRequest(req, metadata.thinking)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var want any = effort
|
|
switch effort {
|
|
case "":
|
|
want = nil
|
|
case "none":
|
|
want = false
|
|
case "future", " HIGH ":
|
|
default:
|
|
if metadata.boolean {
|
|
want = true
|
|
}
|
|
}
|
|
var actual any
|
|
if got.Think != nil {
|
|
actual = got.Think.Value
|
|
}
|
|
if actual != want {
|
|
t.Fatalf("thinking=%#v, want %#v", actual, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|