mirror of
https://github.com/ollama/ollama.git
synced 2026-04-27 19:25:55 +02:00
Compare commits
2 Commits
v0.21.2
...
v0.21.3-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea01af6f76 | ||
|
|
c2ebb4d57c |
14
api/types.go
14
api/types.go
@@ -1080,7 +1080,7 @@ func DefaultOptions() Options {
|
||||
}
|
||||
}
|
||||
|
||||
// ThinkValue represents a value that can be a boolean or a string ("high", "medium", "low")
|
||||
// ThinkValue represents a value that can be a boolean or a string ("high", "medium", "low", "max")
|
||||
type ThinkValue struct {
|
||||
// Value can be a bool or string
|
||||
Value interface{}
|
||||
@@ -1096,7 +1096,7 @@ func (t *ThinkValue) IsValid() bool {
|
||||
case bool:
|
||||
return true
|
||||
case string:
|
||||
return v == "high" || v == "medium" || v == "low"
|
||||
return v == "high" || v == "medium" || v == "low" || v == "max"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -1130,8 +1130,8 @@ func (t *ThinkValue) Bool() bool {
|
||||
case bool:
|
||||
return v
|
||||
case string:
|
||||
// Any string value ("high", "medium", "low") means thinking is enabled
|
||||
return v == "high" || v == "medium" || v == "low"
|
||||
// Any string value ("high", "medium", "low", "max") means thinking is enabled
|
||||
return v == "high" || v == "medium" || v == "low" || v == "max"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -1169,14 +1169,14 @@ func (t *ThinkValue) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err == nil {
|
||||
// Validate string values
|
||||
if s != "high" && s != "medium" && s != "low" {
|
||||
return fmt.Errorf("invalid think value: %q (must be \"high\", \"medium\", \"low\", true, or false)", s)
|
||||
if s != "high" && s != "medium" && s != "low" && s != "max" {
|
||||
return fmt.Errorf("invalid think value: %q (must be \"high\", \"medium\", \"low\", \"max\", true, or false)", s)
|
||||
}
|
||||
t.Value = s
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("think must be a boolean or string (\"high\", \"medium\", \"low\", true, or false)")
|
||||
return fmt.Errorf("think must be a boolean or string (\"high\", \"medium\", \"low\", \"max\", true, or false)")
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler
|
||||
|
||||
@@ -495,6 +495,11 @@ func TestThinking_UnmarshalJSON(t *testing.T) {
|
||||
input: `{ "think": "low" }`,
|
||||
expectedThinking: &ThinkValue{Value: "low"},
|
||||
},
|
||||
{
|
||||
name: "string_max",
|
||||
input: `{ "think": "max" }`,
|
||||
expectedThinking: &ThinkValue{Value: "max"},
|
||||
},
|
||||
{
|
||||
name: "invalid_string",
|
||||
input: `{ "think": "invalid" }`,
|
||||
|
||||
@@ -582,10 +582,10 @@ func RunHandler(cmd *cobra.Command, args []string) error {
|
||||
opts.Think = &api.ThinkValue{Value: true}
|
||||
case "false":
|
||||
opts.Think = &api.ThinkValue{Value: false}
|
||||
case "high", "medium", "low":
|
||||
case "high", "medium", "low", "max":
|
||||
opts.Think = &api.ThinkValue{Value: thinkStr}
|
||||
default:
|
||||
return fmt.Errorf("invalid value for --think: %q (must be true, false, high, medium, or low)", thinkStr)
|
||||
return fmt.Errorf("invalid value for --think: %q (must be true, false, high, medium, low, or max)", thinkStr)
|
||||
}
|
||||
} else {
|
||||
opts.Think = nil
|
||||
|
||||
@@ -632,8 +632,8 @@ func FromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
|
||||
}
|
||||
|
||||
if effort != "" {
|
||||
if !slices.Contains([]string{"high", "medium", "low", "none"}, effort) {
|
||||
return nil, fmt.Errorf("invalid reasoning value: '%s' (must be \"high\", \"medium\", \"low\", or \"none\")", effort)
|
||||
if !slices.Contains([]string{"high", "medium", "low", "max", "none"}, effort) {
|
||||
return nil, fmt.Errorf("invalid reasoning value: '%s' (must be \"high\", \"medium\", \"low\", \"max\", or \"none\")", effort)
|
||||
}
|
||||
|
||||
if effort == "none" {
|
||||
|
||||
@@ -55,6 +55,57 @@ func TestFromChatRequest_Basic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromChatRequest_ReasoningEffort(t *testing.T) {
|
||||
effort := func(s string) *string { return &s }
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
effort *string
|
||||
want any // expected ThinkValue.Value; nil means req.Think should be nil
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "unset", effort: nil, want: nil},
|
||||
{name: "high", effort: effort("high"), want: "high"},
|
||||
{name: "medium", effort: effort("medium"), want: "medium"},
|
||||
{name: "low", effort: effort("low"), want: "low"},
|
||||
{name: "max", effort: effort("max"), want: "max"},
|
||||
{name: "none disables", effort: effort("none"), want: false},
|
||||
{name: "invalid", effort: effort("extreme"), wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := ChatCompletionRequest{
|
||||
Model: "test-model",
|
||||
Messages: []Message{{Role: "user", Content: "hi"}},
|
||||
ReasoningEffort: tc.effort,
|
||||
}
|
||||
result, err := FromChatRequest(req)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for effort=%v, got none", *tc.effort)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if tc.want == nil {
|
||||
if result.Think != nil {
|
||||
t.Fatalf("expected nil Think, got %+v", result.Think)
|
||||
}
|
||||
return
|
||||
}
|
||||
if result.Think == nil {
|
||||
t.Fatalf("expected Think=%v, got nil", tc.want)
|
||||
}
|
||||
if result.Think.Value != tc.want {
|
||||
t.Fatalf("got Think.Value=%v, want %v", result.Think.Value, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromChatRequest_WithImage(t *testing.T) {
|
||||
imgData, _ := base64.StdEncoding.DecodeString(image)
|
||||
|
||||
|
||||
@@ -525,6 +525,18 @@ func FromResponsesRequest(r ResponsesRequest) (*api.ChatRequest, error) {
|
||||
options["num_predict"] = *r.MaxOutputTokens
|
||||
}
|
||||
|
||||
var think *api.ThinkValue
|
||||
if effort := r.Reasoning.Effort; effort != "" {
|
||||
switch effort {
|
||||
case "none":
|
||||
think = &api.ThinkValue{Value: false}
|
||||
case "low", "medium", "high", "max":
|
||||
think = &api.ThinkValue{Value: effort}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid reasoning value: %q (must be \"high\", \"medium\", \"low\", \"max\", or \"none\")", effort)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert tools from Responses API format to api.Tool format
|
||||
var tools []api.Tool
|
||||
for _, t := range r.Tools {
|
||||
@@ -552,6 +564,7 @@ func FromResponsesRequest(r ResponsesRequest) (*api.ChatRequest, error) {
|
||||
Options: options,
|
||||
Tools: tools,
|
||||
Format: format,
|
||||
Think: think,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -415,6 +415,86 @@ func TestFromResponsesRequest_Tools(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromResponsesRequest_ReasoningEffort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
effort string
|
||||
wantThink any
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
},
|
||||
{
|
||||
name: "low",
|
||||
effort: "low",
|
||||
wantThink: "low",
|
||||
},
|
||||
{
|
||||
name: "medium",
|
||||
effort: "medium",
|
||||
wantThink: "medium",
|
||||
},
|
||||
{
|
||||
name: "high",
|
||||
effort: "high",
|
||||
wantThink: "high",
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
effort: "max",
|
||||
wantThink: "max",
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
effort: "none",
|
||||
wantThink: false,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
effort: "extreme",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := ResponsesRequest{
|
||||
Model: "deepseek-v4-flash",
|
||||
Input: ResponsesInput{Text: "hi"},
|
||||
}
|
||||
if tt.effort != "" {
|
||||
req.Reasoning.Effort = tt.effort
|
||||
}
|
||||
|
||||
chatReq, err := FromResponsesRequest(req)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if tt.wantThink == nil {
|
||||
if chatReq.Think != nil {
|
||||
t.Fatalf("Think = %#v, want nil", chatReq.Think)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if chatReq.Think == nil {
|
||||
t.Fatalf("Think = nil, want %v", tt.wantThink)
|
||||
}
|
||||
if chatReq.Think.Value != tt.wantThink {
|
||||
t.Errorf("Think.Value = %v, want %v", chatReq.Think.Value, tt.wantThink)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromResponsesRequest_FunctionCallOutput(t *testing.T) {
|
||||
// Test a complete tool call round-trip:
|
||||
// 1. User message asking about weather
|
||||
|
||||
@@ -375,8 +375,16 @@ func (s *Server) GenerateHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
var builtinParser parsers.Parser
|
||||
if shouldUseHarmony(m) && m.Config.Parser == "" {
|
||||
m.Config.Parser = "harmony"
|
||||
if shouldUseHarmony(m) {
|
||||
// harmony's Reasoning field only understands low/medium/high; map "max" to "high"
|
||||
if req.Think != nil {
|
||||
if s, ok := req.Think.Value.(string); ok && s == "max" {
|
||||
req.Think.Value = "high"
|
||||
}
|
||||
}
|
||||
if m.Config.Parser == "" {
|
||||
m.Config.Parser = "harmony"
|
||||
}
|
||||
}
|
||||
|
||||
if !req.Raw && m.Config.Parser != "" {
|
||||
@@ -2320,8 +2328,16 @@ func (s *Server) ChatHandler(c *gin.Context) {
|
||||
}
|
||||
msgs = filterThinkTags(msgs, m)
|
||||
|
||||
if shouldUseHarmony(m) && m.Config.Parser == "" {
|
||||
m.Config.Parser = "harmony"
|
||||
if shouldUseHarmony(m) {
|
||||
// harmony's Reasoning field only understands low/medium/high; map "max" to "high"
|
||||
if req.Think != nil {
|
||||
if s, ok := req.Think.Value.(string); ok && s == "max" {
|
||||
req.Think.Value = "high"
|
||||
}
|
||||
}
|
||||
if m.Config.Parser == "" {
|
||||
m.Config.Parser = "harmony"
|
||||
}
|
||||
}
|
||||
|
||||
var builtinParser parsers.Parser
|
||||
|
||||
Reference in New Issue
Block a user