mirror of
https://github.com/hanzoai/thinking.git
synced 2026-10-30 14:55:31 +00:00
145 lines
4.5 KiB
Go
145 lines
4.5 KiB
Go
// Copyright 2026 Hanzo AI Inc. All Rights Reserved.
|
|
|
|
package thinking
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
// Both wire dialects normalize into the same Depth ordinal: an Anthropic budget and
|
|
// an OpenAI effort that mean the same thing land on the same value.
|
|
func TestConstructors(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
got Depth
|
|
want Depth
|
|
}{
|
|
{"budget 0 → off", Budget(0), Off},
|
|
{"budget negative → off", Budget(-1), Off},
|
|
{"budget 2k → low", Budget(2048), Low},
|
|
{"budget 4k → mid", Budget(4096), Mid},
|
|
{"budget 10k → mid", Budget(10000), Mid},
|
|
{"budget 16k → max", Budget(16384), Max},
|
|
{"budget 32k → max", Budget(32000), Max},
|
|
{"effort '' → off", Effort(""), Off},
|
|
{"effort low", Effort("low"), Low},
|
|
{"effort medium → mid", Effort("medium"), Mid},
|
|
{"effort high", Effort("high"), High},
|
|
{"effort max", Effort("max"), Max},
|
|
{"effort junk → off", Effort("turbo"), Off},
|
|
}
|
|
for _, c := range cases {
|
|
if c.got != c.want {
|
|
t.Errorf("%s: got %d, want %d", c.name, c.got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOf(t *testing.T) {
|
|
cases := map[string]Vocab{
|
|
"glm-5.2": GLM,
|
|
"deepseek-v4-pro": GLM,
|
|
"deepseek-4-flash": GLM,
|
|
"minimax-m2.5": GLM,
|
|
"llama3.3-70b": GLM,
|
|
"qwen3.5-397b-a17b": Qwen,
|
|
"alibaba-qwen3-32b": Qwen,
|
|
"zen5.8": Zen,
|
|
"zen5-flash": Zen,
|
|
"kimi-k2.6": Kimi,
|
|
"gpt-5.3-codex": OpenAI,
|
|
"o3-mini": OpenAI,
|
|
"something-unknown": GLM,
|
|
}
|
|
for id, want := range cases {
|
|
if got := Of(id); got != want {
|
|
t.Errorf("Of(%q) = %q, want %q", id, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The reasoning_effort ordinal projection: GLM has only {high,max}; OpenAI has
|
|
// {low,medium,high} and no max.
|
|
func TestOrdinal(t *testing.T) {
|
|
cases := []struct {
|
|
depth Depth
|
|
vocab Vocab
|
|
want string
|
|
}{
|
|
{Off, GLM, "none"}, // GLM reasons by default; Off must send none to silence it
|
|
{Low, GLM, "high"}, // GLM coerces sub-max up to high
|
|
{Mid, GLM, "high"},
|
|
{High, GLM, "high"},
|
|
{Max, GLM, "max"},
|
|
{Off, OpenAI, ""}, // OpenAI reasons only when asked; Off writes no field
|
|
{Low, OpenAI, "low"},
|
|
{Mid, OpenAI, "medium"},
|
|
{High, OpenAI, "high"},
|
|
{Max, OpenAI, "high"}, // OpenAI has no max; Max coerces down to high
|
|
}
|
|
for _, c := range cases {
|
|
if got := c.depth.ordinal(c.vocab); got != c.want {
|
|
t.Errorf("%d ordinal %s = %q, want %q", c.depth, c.vocab, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fields projects the depth into each upstream's native request shape.
|
|
func TestFields(t *testing.T) {
|
|
j := func(m map[string]any) string {
|
|
b, _ := json.Marshal(m)
|
|
return string(b)
|
|
}
|
|
|
|
// GLM / OpenAI → reasoning_effort.
|
|
if got := j(Max.Fields(GLM)); got != `{"reasoning_effort":"max"}` {
|
|
t.Errorf("Max GLM → %s", got)
|
|
}
|
|
if got := j(Mid.Fields(OpenAI)); got != `{"reasoning_effort":"medium"}` {
|
|
t.Errorf("Mid OpenAI → %s", got)
|
|
}
|
|
// Off on a GLM-family upstream must actively disable reasoning: these models reason
|
|
// by default, so an absent field streams a content:null reasoning preamble that reads
|
|
// as an empty completion. Off → reasoning_effort:"none".
|
|
if got := j(Off.Fields(GLM)); got != `{"reasoning_effort":"none"}` {
|
|
t.Errorf("Off GLM → %s, want reasoning_effort:none", got)
|
|
}
|
|
// Off writes nothing on vocabularies whose upstream defaults OFF (o-series reasons
|
|
// only when asked; Qwen/Kimi are explicit gates).
|
|
for _, v := range []Vocab{OpenAI, Qwen, Kimi} {
|
|
if got := j(Off.Fields(v)); got != `{}` {
|
|
t.Errorf("Off %q → %s, want {}", v, got)
|
|
}
|
|
}
|
|
// Zen reasons by default; Off actively silences thinking.
|
|
if got := j(Off.Fields(Zen)); got != `{"enable_thinking":false}` {
|
|
t.Errorf("Off Zen → %s, want enable_thinking:false", got)
|
|
}
|
|
if got := j(High.Fields(Zen)); got != `{"enable_thinking":true}` {
|
|
t.Errorf("High Zen → %s", got)
|
|
}
|
|
// Qwen → enable_thinking gate (no budget cap: the ordinal has none).
|
|
if got := j(High.Fields(Qwen)); got != `{"enable_thinking":true}` {
|
|
t.Errorf("High Qwen → %s", got)
|
|
}
|
|
// Kimi → thinking object; only Max preserves the trace across turns.
|
|
if got := j(High.Fields(Kimi)); got != `{"thinking":{"type":"enabled"}}` {
|
|
t.Errorf("High Kimi → %s", got)
|
|
}
|
|
if got := j(Max.Fields(Kimi)); got != `{"thinking":{"preserve_thinking":"all","type":"enabled"}}` {
|
|
t.Errorf("Max Kimi → %s", got)
|
|
}
|
|
}
|
|
|
|
func TestOn(t *testing.T) {
|
|
if Off.On() {
|
|
t.Error("Off.On() = true, want false")
|
|
}
|
|
for _, d := range []Depth{Low, Mid, High, Max} {
|
|
if !d.On() {
|
|
t.Errorf("%d.On() = false, want true", d)
|
|
}
|
|
}
|
|
}
|