Download internal/upstream/payload_test.go from a3216/gcli2api: direct link, hf CLI and curl.
- Browser
- Download file 19.7 kB
-
https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/upstream/payload_test.go
- Command line
-
hf download hf://spaces/a3216/gcli2api/internal/upstream/payload_test.go
-
curl -L -o payload_test.go https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/upstream/payload_test.go
19.7 kB
| package upstream | |
| import ( | |
| "encoding/json" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| ) | |
| // TestNormalizeRoles 验证出站请求体把 developer 角色归一为 system。 | |
| // 上游 role 白名单不含 developer(OpenAI 新规范的 system 别名), | |
| // 命中即 HTTP 400 code=11128;此处走 PrepareBodyOptWithEfforts 全链路断言。 | |
| func TestNormalizeRoles(t *testing.T) { | |
| cases := []struct { | |
| name string | |
| body string | |
| wantRoles []string // 与输出 messages 逐条对应的期望 role;len 即消息数 | |
| }{ | |
| {"developer 改写为 system", | |
| `{"messages":[{"role":"developer","content":"x"}]}`, []string{"system"}}, | |
| {"Developer 首字母大写改写", | |
| `{"messages":[{"role":"Developer","content":"x"}]}`, []string{"system"}}, | |
| {"DEVELOPER 全大写改写", | |
| `{"messages":[{"role":"DEVELOPER","content":"x"}]}`, []string{"system"}}, | |
| {"前后空白 TrimSpace 后改写", | |
| `{"messages":[{"role":" developer ","content":"x"}]}`, []string{"system"}}, | |
| {"system 原样保留", | |
| `{"messages":[{"role":"system","content":"x"}]}`, []string{"system"}}, | |
| {"user 原样保留", | |
| `{"messages":[{"role":"user","content":"x"}]}`, []string{"user"}}, | |
| {"assistant 原样保留", | |
| `{"messages":[{"role":"assistant","content":"x"}]}`, []string{"assistant"}}, | |
| {"tool 原样保留(不因未知而改写)", | |
| `{"messages":[{"role":"tool","content":"x"}]}`, []string{"tool"}}, | |
| {"messages 缺失不 panic 且其余字段不变", | |
| `{"model":"glm-5.2"}`, []string{}}, | |
| {"messages 为空数组不 panic", | |
| `{"messages":[]}`, []string{}}, | |
| {"混合消息仅 developer 被改写", | |
| `{"messages":[{"role":"developer","content":"a"},{"role":"user","content":"b"},{"role":"developer","content":"c"}]}`, | |
| []string{"system", "user", "system"}}, | |
| {"sanitize=false 时仍归一(与脱敏解耦)", | |
| `{"messages":[{"role":"developer","content":"x"}]}`, []string{"system"}}, | |
| {"非对象消息元素跳过、其余正常处理", | |
| `{"messages":["str",{"role":"developer","content":"x"},42]}`, []string{"system"}}, | |
| } | |
| for _, c := range cases { | |
| t.Run(c.name, func(t *testing.T) { | |
| // 全程 sanitize=false:验证 role 归一与内容脱敏开关无关(D4)。 | |
| out := PrepareBodyOptWithEfforts([]byte(c.body), false, nil) | |
| var obj map[string]any | |
| if err := json.Unmarshal(out, &obj); err != nil { | |
| t.Fatalf("unmarshal: %v (out=%s)", err, out) | |
| } | |
| // 提取输出 messages 里的 role(非对象元素跳过,不 panic)。 | |
| var got []string | |
| if msgs, ok := obj["messages"].([]any); ok { | |
| for _, m := range msgs { | |
| msg, ok := m.(map[string]any) | |
| if !ok { | |
| continue | |
| } | |
| if role, ok := msg["role"].(string); ok { | |
| got = append(got, role) | |
| } | |
| } | |
| } | |
| if len(got) != len(c.wantRoles) { | |
| t.Fatalf("role 数量不符: got %v (%d) want %v (%d)", got, len(got), c.wantRoles, len(c.wantRoles)) | |
| } | |
| for i := range got { | |
| if got[i] != c.wantRoles[i] { | |
| t.Errorf("role[%d] = %q want %q", i, got[i], c.wantRoles[i]) | |
| } | |
| } | |
| }) | |
| } | |
| // messages 缺失时,其余字段必须原样保留(除强制 stream)。 | |
| t.Run("messages 缺失时其余字段不变", func(t *testing.T) { | |
| out := PrepareBodyOptWithEfforts([]byte(`{"model":"glm-5.2","temperature":0.7}`), false, nil) | |
| var obj map[string]any | |
| if err := json.Unmarshal(out, &obj); err != nil { | |
| t.Fatalf("unmarshal: %v", err) | |
| } | |
| if obj["model"] != "glm-5.2" || obj["temperature"] != 0.7 { | |
| t.Errorf("其余字段被改动: %v", obj) | |
| } | |
| }) | |
| } | |
| func TestPrepareBodyOptWithEfforts(t *testing.T) { | |
| efforts := map[string][]string{ | |
| "glm-5.2": {"off", "low", "high"}, | |
| "glm-5.2-mini": {"low", "medium"}, | |
| "glm-5.2-max": {"high", "xhigh"}, | |
| } | |
| cases := []struct { | |
| name string | |
| body string | |
| efforts map[string][]string | |
| wantKey string // 输出应带有的 effort 字段名;空表示该字段应不存在 | |
| wantVal string // 期望值 | |
| }{ | |
| {"downgrade to highest supported at or below request", | |
| `{"model":"glm-5.2-mini","reasoning_effort":"high"}`, efforts, "reasoning_effort", "medium"}, | |
| {"floor to lowest when all supported above request", | |
| `{"model":"glm-5.2-max","reasoning_effort":"low"}`, efforts, "reasoning_effort", "high"}, | |
| {"supported effort passes through unchanged", | |
| `{"model":"glm-5.2","reasoning_effort":"low"}`, efforts, "reasoning_effort", "low"}, | |
| {"camelCase field name downgrades and keeps key", | |
| `{"model":"glm-5.2-mini","reasoningEffort":"high"}`, efforts, "reasoningEffort", "medium"}, | |
| {"unknown model passes through", | |
| `{"model":"unknown","reasoning_effort":"max"}`, efforts, "reasoning_effort", "max"}, | |
| {"unknown effort value passes through", | |
| `{"model":"glm-5.2","reasoning_effort":"ultra"}`, efforts, "reasoning_effort", "ultra"}, | |
| {"empty cache passes through", | |
| `{"model":"glm-5.2","reasoning_effort":"max"}`, map[string][]string{}, "reasoning_effort", "max"}, | |
| {"no effort field untouched", | |
| `{"model":"glm-5.2-mini","messages":[]}`, efforts, "", ""}, | |
| {"nil efforts map passes through", | |
| `{"model":"glm-5.2","reasoning_effort":"max"}`, nil, "reasoning_effort", "max"}, | |
| } | |
| for _, c := range cases { | |
| t.Run(c.name, func(t *testing.T) { | |
| out := PrepareBodyOptWithEfforts([]byte(c.body), false, c.efforts) | |
| var m map[string]any | |
| if err := json.Unmarshal(out, &m); err != nil { | |
| t.Fatalf("unmarshal: %v (body=%s)", err, out) | |
| } | |
| if c.wantKey == "" { | |
| if _, ok := m["reasoning_effort"]; ok { | |
| t.Errorf("reasoning_effort should be absent, got %v", m["reasoning_effort"]) | |
| } | |
| if _, ok := m["reasoningEffort"]; ok { | |
| t.Errorf("reasoningEffort should be absent, got %v", m["reasoningEffort"]) | |
| } | |
| return | |
| } | |
| got, ok := m[c.wantKey].(string) | |
| if !ok || got != c.wantVal { | |
| t.Errorf("%s: got %v (%T) want %q", c.wantKey, m[c.wantKey], m[c.wantKey], c.wantVal) | |
| } | |
| }) | |
| } | |
| } | |
| // TestPrepareBodyStreamOptions body 未显式带 stream_options 时注入 | |
| // {include_usage: true}(D7,官方 CLI 流式必发);body 已带则不覆盖。 | |
| func TestPrepareBodyStreamOptions(t *testing.T) { | |
| out := PrepareBodyOptWithEfforts([]byte(`{"model":"glm-5.2","messages":[]}`), false, nil) | |
| var obj map[string]any | |
| if err := json.Unmarshal(out, &obj); err != nil { | |
| t.Fatalf("unmarshal: %v (out=%s)", err, out) | |
| } | |
| so, ok := obj["stream_options"].(map[string]any) | |
| if !ok { | |
| t.Fatalf("stream_options not injected: %v", obj["stream_options"]) | |
| } | |
| if so["include_usage"] != true { | |
| t.Errorf("stream_options.include_usage = %v want true", so["include_usage"]) | |
| } | |
| out2 := PrepareBodyOptWithEffertsPreserve(t, `{"model":"glm-5.2","messages":[],"stream_options":{"include_usage":false}}`) | |
| obj2, err := decodeBody(out2) | |
| if err != nil { | |
| t.Fatalf("unmarshal: %v", err) | |
| } | |
| so2, ok := obj2["stream_options"].(map[string]any) | |
| if !ok { | |
| t.Fatalf("stream_options lost: %v", obj2["stream_options"]) | |
| } | |
| if so2["include_usage"] != false { | |
| t.Errorf("stream_options.include_usage = %v want false (not overwritten)", so2["include_usage"]) | |
| } | |
| } | |
| // PrepareBodyOptWithEffertsPreserve helper:PrepareBodyOptWithEfforts 包装。 | |
| func PrepareBodyOptWithEffertsPreserve(t *testing.T, body string) []byte { | |
| t.Helper() | |
| return PrepareBodyOptWithEfforts([]byte(body), false, nil) | |
| } | |
| // decodeBody helper:解析 body JSON。 | |
| func decodeBody(b []byte) (map[string]any, error) { | |
| var obj map[string]any | |
| err := json.Unmarshal(b, &obj) | |
| return obj, err | |
| } | |
| // TestPrepareBodyDeterministic 序列化稳定性:同输入跑多遍出站字节级一致 | |
| // (prompt_cache_key 前缀命中的前提——链中不得注入时间/随机/ID 类不确定源)。 | |
| func TestPrepareBodyDeterministic(t *testing.T) { | |
| inputs := []string{ | |
| `{"model":"glm-5.2","messages":[{"role":"system","content":"你是助手"},{"role":"user","content":"你好"}],"reasoning_effort":"high"}`, | |
| `{"model":"deepseek-v4","messages":[{"role":"user","content":"写个函数"}],"tool_choice":{"type":"auto"},"tools":[{"type":"function","function":{"name":"f"}}]}`, | |
| `{"model":"glm-5.3","messages":[{"role":"developer","content":"sys"},{"role":"user","content":[{"type":"text","text":"hi"}]}]}`, | |
| } | |
| for i, in := range inputs { | |
| var first []byte | |
| for round := 0; round < 5; round++ { | |
| out := PrepareBodyOptWithEfforts([]byte(in), true, map[string][]string{"glm-5.2": {"off", "low", "high"}}) | |
| if round == 0 { | |
| first = out | |
| continue | |
| } | |
| if string(out) != string(first) { | |
| t.Fatalf("input #%d round %d differs from round 0:\n%s\n%s", i, round, first, out) | |
| } | |
| } | |
| } | |
| } | |
| // TestNormalizeImageURL 覆盖 OpenAI chat 多模态内容的 image_url 兼容: | |
| // 字符串形态必须转为上游需要的对象形态;对象形态及其中字段必须原样保留; | |
| // 无效输入不补默认值,继续交给上游返回真实错误。 | |
| func TestNormalizeImageURL(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| body string | |
| want any | |
| }{ | |
| { | |
| name: "data url string to object", | |
| body: `{"messages":[{"role":"user","content":[{"type":"text","text":"look"},{"type":"image_url","image_url":"data:image/png;base64,QUJD"}]}]}`, | |
| want: map[string]any{"url": "data:image/png;base64,QUJD"}, | |
| }, | |
| { | |
| name: "http url string to object", | |
| body: `{"messages":[{"role":"user","content":[{"type":"image_url","image_url":"https://example.test/a.png"}]}]}`, | |
| want: map[string]any{"url": "https://example.test/a.png"}, | |
| }, | |
| { | |
| name: "object with detail preserved", | |
| body: `{"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":"data:image/png;base64,QUJD","detail":"low","mime_type":"image/png"}}]}]}`, | |
| want: map[string]any{"url": "data:image/png;base64,QUJD", "detail": "low", "mime_type": "image/png"}, | |
| }, | |
| { | |
| name: "invalid object url type preserved", | |
| body: `{"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":123}}]}]}`, | |
| want: map[string]any{"url": float64(123)}, | |
| }, | |
| { | |
| name: "missing image url preserved", | |
| body: `{"messages":[{"role":"user","content":[{"type":"image_url"}]}]}`, | |
| want: nil, | |
| }, | |
| { | |
| name: "empty string preserved", | |
| body: `{"messages":[{"role":"user","content":[{"type":"image_url","image_url":""}]}]}`, | |
| want: "", | |
| }, | |
| } | |
| for _, tc := range tests { | |
| t.Run(tc.name, func(t *testing.T) { | |
| for _, sanitize := range []bool{false, true} { | |
| out := PrepareBodyOptWithEfforts([]byte(tc.body), sanitize, nil) | |
| obj, err := decodeBody(out) | |
| if err != nil { | |
| t.Fatalf("sanitize=%v unmarshal: %v (out=%s)", sanitize, err, out) | |
| } | |
| msgs := obj["messages"].([]any) | |
| content := msgs[0].(map[string]any)["content"].([]any) | |
| var part map[string]any | |
| for _, rawPart := range content { | |
| candidate, ok := rawPart.(map[string]any) | |
| if ok && candidate["type"] == "image_url" { | |
| part = candidate | |
| break | |
| } | |
| } | |
| if part == nil { | |
| t.Fatal("image_url part not found") | |
| } | |
| if tc.want == nil { | |
| if _, exists := part["image_url"]; exists { | |
| t.Fatalf("sanitize=%v: missing image_url should stay missing, got %#v", sanitize, part) | |
| } | |
| continue | |
| } | |
| if got := part["image_url"]; !reflect.DeepEqual(got, tc.want) { | |
| t.Errorf("sanitize=%v: image_url=%#v want %#v", sanitize, got, tc.want) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| // TestNormalizeToolPatterns 工具 schema pattern 的 `\_` 转义归一(11129 实案: | |
| // exa agent_run 的 `^agent\_run\_` 被 deepseek 系确定性拒收,归一后上游 200)。 | |
| // 覆盖:function.parameters 与裸 parameters 两形态、嵌套 schema、patternProperties | |
| // 键、消息正文不碰、无 tools no-op、sanitize 开关两态一致。 | |
| func TestNormalizeToolPatterns(t *testing.T) { | |
| lookupPattern := func(t *testing.T, body string, path ...any) string { | |
| t.Helper() | |
| var obj map[string]any | |
| if err := json.Unmarshal([]byte(body), &obj); err != nil { | |
| t.Fatalf("out not json: %v", err) | |
| } | |
| cur := any(obj) | |
| for _, p := range path { | |
| switch k := p.(type) { | |
| case string: | |
| m, ok := cur.(map[string]any) | |
| if !ok { | |
| t.Fatalf("path %v: want object, got %T", path, cur) | |
| } | |
| cur = m[k] | |
| case int: | |
| l, ok := cur.([]any) | |
| if !ok || k >= len(l) { | |
| t.Fatalf("path %v: bad array at %T", path, cur) | |
| } | |
| cur = l[k] | |
| } | |
| } | |
| s, _ := cur.(string) | |
| return s | |
| } | |
| t.Run("exa agent_run pattern normalized", func(t *testing.T) { | |
| bs := string(byte(92)) // 反斜杠,测试体经工具链多层转义易被吞,运行时拼装保真 | |
| body := `{"model":"deepseek-v4.1-flash","messages":[{"role":"user","content":"hi"}],"tools":[{"type":"function","function":{"name":"agent_run","parameters":{"type":"object","properties":{"runId":{"type":"string","pattern":"^agent` + bs + bs + `_run` + bs + bs + `_"}` + `,"query":{"type":"string"}},"required":["query"]}}}]}` //nolint:lll // 实案 body 原样 | |
| out := string(PrepareBodyOptWithEfforts([]byte(body), false, nil)) | |
| if got := lookupPattern(t, out, "tools", 0, "function", "parameters", "properties", "runId", "pattern"); got != `^agent_run_` { | |
| t.Fatalf("pattern = %q, want %q", got, `^agent_run_`) | |
| } | |
| // 同 schema 的兄弟字段不受影响 | |
| if got := lookupPattern(t, out, "tools", 0, "function", "parameters", "properties", "query", "type"); got != "string" { | |
| t.Fatalf("sibling field disturbed: %q", got) | |
| } | |
| }) | |
| t.Run("nested anyOf items pattern normalized", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"messages":[],"tools":[{"type":"function","function":{"name":"t","parameters":{"anyOf":[{"properties":{"code":{"pattern":"x` + bs + bs + `_y"}}},{"type":"string"}]}}}]}` //nolint:lll | |
| out := string(PrepareBodyOptWithEfforts([]byte(body), false, nil)) | |
| if got := lookupPattern(t, out, "tools", 0, "function", "parameters", "anyOf", 0, "properties", "code", "pattern"); got != "x_y" { | |
| t.Fatalf("nested pattern = %q, want %q", got, "x_y") | |
| } | |
| }) | |
| t.Run("bare tool parameters form normalized", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"messages":[],"tools":[{"name":"t","parameters":{"properties":{"id":{"pattern":"p` + bs + bs + `_q"}}}}]}` | |
| out := string(PrepareBodyOptWithEfforts([]byte(body), false, nil)) | |
| if got := lookupPattern(t, out, "tools", 0, "parameters", "properties", "id", "pattern"); got != "p_q" { | |
| t.Fatalf("bare parameters pattern = %q, want %q", got, "p_q") | |
| } | |
| }) | |
| t.Run("patternProperties key normalized", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"messages":[],"tools":[{"type":"function","function":{"name":"t","parameters":{"patternProperties":{"^a` + bs + bs + `_b` + bs + bs + `_":{"type":"string"}}}}}]}` //nolint:lll | |
| var obj map[string]any | |
| out := PrepareBodyOptWithEfforts([]byte(body), false, nil) | |
| if err := json.Unmarshal(out, &obj); err != nil { | |
| t.Fatalf("out not json: %v", err) | |
| } | |
| tool := obj["tools"].([]any)[0].(map[string]any)["function"].(map[string]any)["parameters"].(map[string]any) | |
| props := tool["patternProperties"].(map[string]any) | |
| if _, ok := props["^a_b_"]; !ok { | |
| t.Fatalf("normalized key missing: %v", props) | |
| } | |
| for k := range props { | |
| if strings.Contains(k, string(byte(92))) { | |
| t.Fatalf("patternProperties key not normalized: %q", k) | |
| } | |
| } | |
| }) | |
| t.Run("message content backslash untouched", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| raw := `{"messages":[{"role":"user","content":"path C:` + bs + bs + `_dir and regex a` + bs + bs + `_b"}],"tools":[{"type":"function","function":{"name":"t","parameters":{"properties":{"p":{"pattern":"q` + bs + bs + `_r"}}}}}]}` //nolint:lll | |
| out := string(PrepareBodyOptWithEfforts([]byte(raw), false, nil)) | |
| var obj map[string]any | |
| if err := json.Unmarshal([]byte(out), &obj); err != nil { | |
| t.Fatalf("out not json: %v", err) | |
| } | |
| msg := obj["messages"].([]any)[0].(map[string]any) | |
| if got := msg["content"]; got != `path C:\_dir and regex a\_b` { | |
| t.Fatalf("message content mutated: %q", got) | |
| } | |
| }) | |
| t.Run("clean pattern untouched", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"messages":[],"tools":[{"type":"function","function":{"name":"t","parameters":{"properties":{"p":{"pattern":"^[a-z]+` + bs + bs + `d_$"}}}}}]}` //nolint:lll | |
| out := string(PrepareBodyOptWithEfforts([]byte(body), false, nil)) | |
| if got := lookupPattern(t, out, "tools", 0, "function", "parameters", "properties", "p", "pattern"); got != `^[a-z]+\d_$` { | |
| t.Fatalf("clean pattern mutated: %q", got) | |
| } | |
| }) | |
| t.Run("no tools is no-op", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"model":"m","messages":[{"role":"user","content":"a` + bs + bs + `_b"}]}` | |
| var inObj, outObj map[string]any | |
| if err := json.Unmarshal([]byte(body), &inObj); err != nil { | |
| t.Fatal(err) | |
| } | |
| out := PrepareBodyOptWithEfforts([]byte(body), false, nil) | |
| if err := json.Unmarshal(out, &outObj); err != nil { | |
| t.Fatalf("out not json: %v", err) | |
| } | |
| inMsg, _ := json.Marshal(inObj["messages"]) | |
| outMsg, _ := json.Marshal(outObj["messages"]) | |
| if string(inMsg) != string(outMsg) { | |
| t.Fatalf("messages changed without tools: %s -> %s", inMsg, outMsg) | |
| } | |
| if _, has := outObj["tools"]; has { | |
| t.Fatal("tools appeared out of nowhere") | |
| } | |
| }) | |
| t.Run("sanitize on and off behave the same", func(t *testing.T) { | |
| bs := string(byte(92)) | |
| body := `{"messages":[],"tools":[{"type":"function","function":{"name":"t","parameters":{"properties":{"p":{"pattern":"a` + bs + bs + `_b"}}}}}]}` //nolint:lll | |
| for _, sanitize := range []bool{false, true} { | |
| out := string(PrepareBodyOptWithEfforts([]byte(body), sanitize, nil)) | |
| if got := lookupPattern(t, out, "tools", 0, "function", "parameters", "properties", "p", "pattern"); got != "a_b" { | |
| t.Fatalf("sanitize=%v: pattern = %q, want %q", sanitize, got, "a_b") | |
| } | |
| } | |
| }) | |
| } | |
| // TestClampGPTMinMaxTokens GPT 系上游要求 max_tokens ≥ 16(实测 gpt-6-sol/gpt-6-luna/ | |
| // gpt-5.6-sol:15 → 400 code=11133 model_param_invalid,16 → 200)。Claude Code 切 | |
| // 模型时的探针请求 max_tokens 极小,全号轮转同样被拒 → 客户端 503。非 GPT 模型不动。 | |
| func TestClampGPTMinMaxTokens(t *testing.T) { | |
| cases := []struct { | |
| name string | |
| body string | |
| want any // nil 表示字段不存在 | |
| }{ | |
| {"gpt below floor", `{"model":"gpt-6-sol","max_tokens":1,"messages":[]}`, float64(16)}, | |
| {"gpt global prefix", `{"model":"global:gpt-5.6-sol","max_tokens":15,"messages":[]}`, float64(16)}, | |
| {"gpt at floor", `{"model":"gpt-6-luna","max_tokens":16,"messages":[]}`, float64(16)}, | |
| {"gpt above floor", `{"model":"gpt-6-sol","max_tokens":32000,"messages":[]}`, float64(32000)}, | |
| {"gpt alias translated then clamped", `{"model":"gpt-6-sol","max_completion_tokens":1,"messages":[]}`, float64(16)}, | |
| {"gpt absent untouched", `{"model":"gpt-6-sol","messages":[]}`, nil}, | |
| {"non-gpt untouched", `{"model":"hy4-preview-f","max_tokens":1,"messages":[]}`, float64(1)}, | |
| } | |
| for _, c := range cases { | |
| t.Run(c.name, func(t *testing.T) { | |
| obj, err := decodeBody(PrepareBodyOptWithEffertsPreserve(t, c.body)) | |
| if err != nil { | |
| t.Fatalf("unmarshal: %v", err) | |
| } | |
| got, has := obj["max_tokens"] | |
| if c.want == nil { | |
| if has { | |
| t.Fatalf("max_tokens = %v, want absent", got) | |
| } | |
| return | |
| } | |
| if got != c.want { | |
| t.Fatalf("max_tokens = %v, want %v", got, c.want) | |
| } | |
| }) | |
| } | |
| } | |