Skip to content

Commit 9b873cc

Browse files
committed
tests: add 7 deepseek-v4-flash model validation tests
- HTTP-level: verify Flash sends no thinking field, correct model name in request - Flash vs Pro contrast: side-by-side thinking field presence/absence - Full config validation: all 5 defaults applied (Model, BaseURL, Thinking, Iterations, APIKey) - Explicit thinking override: disabled/high stick even when profile DefaultThinking is empty - Profile entry integrity: raw KnownProfiles entry — Prefix, Label, Timeout, MaxContext - ProfileLabel for Flash exact + prefix-match variants Regression gates: catch accidental DefaultThinking addition, context limit changes, or model name mismatches.
1 parent 84b1124 commit 9b873cc

2 files changed

Lines changed: 223 additions & 0 deletions

File tree

internal/llm/client_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,3 +653,96 @@ func TestClient_SimpleCall_EmptyResponse(t *testing.T) {
653653
t.Fatal("expected error for empty choices")
654654
}
655655
}
656+
657+
// ── DeepSeek v4 Flash Model Validation ────────────────────────────────
658+
659+
// TestClient_Call_FlashModelNoThinkingField validates that when using
660+
// deepseek-v4-flash (which has no DefaultThinking), the request body
661+
// does NOT include a "thinking" field. Flash is faster/cheaper by
662+
// skipping extended reasoning — this test guards against accidentally
663+
// sending thinking config to Flash.
664+
func TestClient_Call_FlashModelNoThinkingField(t *testing.T) {
665+
var receivedBody map[string]any
666+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
667+
json.NewDecoder(r.Body).Decode(&receivedBody)
668+
w.Header().Set("Content-Type", "application/json")
669+
w.Write([]byte(`{"choices":[{"message":{"content":"flash response"}}]}`))
670+
}))
671+
defer server.Close()
672+
673+
// Flash: model=deepseek-v4-flash, thinking="" (the default)
674+
c := New(server.URL, "sk-test", "deepseek-v4-flash", "", 0)
675+
result, err := c.Call(context.Background(), []Message{{Role: "user", Content: "hi"}}, nil)
676+
if err != nil {
677+
t.Fatalf("Flash Call() error: %v", err)
678+
}
679+
if result.Content != "flash response" {
680+
t.Errorf("Content = %q, want %q", result.Content, "flash response")
681+
}
682+
683+
// Verify model name is correct in the request
684+
model, ok := receivedBody["model"]
685+
if !ok || model != "deepseek-v4-flash" {
686+
t.Errorf("model = %v, want %q", model, "deepseek-v4-flash")
687+
}
688+
689+
// Verify NO thinking field (Flash doesn't use extended thinking)
690+
if _, ok := receivedBody["thinking"]; ok {
691+
t.Error("Flash request should NOT contain 'thinking' field")
692+
}
693+
if _, ok := receivedBody["reasoning_effort"]; ok {
694+
t.Error("Flash request should NOT contain 'reasoning_effort' field")
695+
}
696+
}
697+
698+
// TestClient_Call_FlashVsProThinkingContrast validates that Flash and Pro
699+
// models are handled differently at the HTTP level:
700+
// - Flash: no thinking field (faster, cheaper)
701+
// - Pro: thinking{type:"enabled"} by default (full reasoning)
702+
func TestClient_Call_FlashVsProThinkingContrast(t *testing.T) {
703+
t.Run("flash_no_thinking", func(t *testing.T) {
704+
var body map[string]any
705+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
706+
json.NewDecoder(r.Body).Decode(&body)
707+
w.Header().Set("Content-Type", "application/json")
708+
w.Write([]byte(`{"choices":[{"message":{"content":"ok"}}]}`))
709+
}))
710+
defer server.Close()
711+
712+
c := New(server.URL, "sk-test", "deepseek-v4-flash", "", 0)
713+
_, err := c.Call(context.Background(), []Message{{Role: "user", Content: "hi"}}, nil)
714+
if err != nil {
715+
t.Fatal(err)
716+
}
717+
if _, ok := body["thinking"]; ok {
718+
t.Error("Flash: thinking field should be absent")
719+
}
720+
})
721+
722+
t.Run("pro_thinking_enabled", func(t *testing.T) {
723+
var body map[string]any
724+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
725+
json.NewDecoder(r.Body).Decode(&body)
726+
w.Header().Set("Content-Type", "application/json")
727+
w.Write([]byte(`{"choices":[{"message":{"content":"ok"}}]}`))
728+
}))
729+
defer server.Close()
730+
731+
c := New(server.URL, "sk-test", "deepseek-v4-pro", "enabled", 0)
732+
_, err := c.Call(context.Background(), []Message{{Role: "user", Content: "hi"}}, nil)
733+
if err != nil {
734+
t.Fatal(err)
735+
}
736+
thinking, ok := body["thinking"]
737+
if !ok {
738+
t.Fatal("Pro: thinking field should be present")
739+
}
740+
thinkingMap, ok := thinking.(map[string]any)
741+
if !ok {
742+
t.Fatal("Pro: thinking should be an object")
743+
}
744+
if thinkingMap["type"] != "enabled" {
745+
t.Errorf("Pro: thinking.type = %v, want 'enabled'", thinkingMap["type"])
746+
}
747+
})
748+
}

kode_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,136 @@ func TestProfileMaxContext_Unknown(t *testing.T) {
531531
}
532532
}
533533

534+
// ── DeepSeek v4 Flash Full-Config Validation ──────────────────────────
535+
536+
// TestNew_FlashModelFullConfig validates every default applied when
537+
// creating an agent with model="deepseek-v4-flash". This is the
538+
// end-to-end gate for Flash model correctness.
539+
func TestNew_FlashModelFullConfig(t *testing.T) {
540+
cfg := Config{
541+
APIKey: "sk-test",
542+
Model: "deepseek-v4-flash",
543+
}
544+
agent, err := New(cfg)
545+
if err != nil {
546+
t.Fatalf("New() with Flash model: %v", err)
547+
}
548+
549+
// Flash profile fields
550+
if agent.config.Model != "deepseek-v4-flash" {
551+
t.Errorf("Model = %q, want %q", agent.config.Model, "deepseek-v4-flash")
552+
}
553+
if agent.config.BaseURL != "https://api.deepseek.com/v1" {
554+
t.Errorf("BaseURL = %q, want %q", agent.config.BaseURL, "https://api.deepseek.com/v1")
555+
}
556+
if agent.config.Thinking != "" {
557+
t.Errorf("Thinking = %q, want empty (Flash has no DefaultThinking)", agent.config.Thinking)
558+
}
559+
if agent.config.MaxIterations != 90 {
560+
t.Errorf("MaxIterations = %d, want 90", agent.config.MaxIterations)
561+
}
562+
if agent.config.APIKey != "sk-test" {
563+
t.Errorf("APIKey = %q, want %q", agent.config.APIKey, "sk-test")
564+
}
565+
}
566+
567+
// TestNew_FlashExplicitThinkingOverridesEmptyDefault validates that an
568+
// explicit Thinking setting wins even when the model's DefaultThinking
569+
// is empty (Flash has no default, so explicit values should stick).
570+
func TestNew_FlashExplicitThinkingOverridesEmptyDefault(t *testing.T) {
571+
t.Run("explicit_disabled", func(t *testing.T) {
572+
cfg := Config{
573+
APIKey: "sk-test",
574+
Model: "deepseek-v4-flash",
575+
Thinking: "disabled",
576+
}
577+
agent, err := New(cfg)
578+
if err != nil {
579+
t.Fatal(err)
580+
}
581+
if agent.config.Thinking != "disabled" {
582+
t.Errorf("Thinking = %q, want 'disabled' (explicit)", agent.config.Thinking)
583+
}
584+
})
585+
586+
t.Run("explicit_high_reasoning", func(t *testing.T) {
587+
cfg := Config{
588+
APIKey: "sk-test",
589+
Model: "deepseek-v4-flash",
590+
Thinking: "high",
591+
}
592+
agent, err := New(cfg)
593+
if err != nil {
594+
t.Fatal(err)
595+
}
596+
if agent.config.Thinking != "high" {
597+
t.Errorf("Thinking = %q, want 'high' (explicit)", agent.config.Thinking)
598+
}
599+
})
600+
}
601+
602+
// TestProfileTimeout_FlashApplied verifies the Flash profile timeout
603+
// (90s) is applied when creating an agent. Unlike Pro's 180s timeout,
604+
// Flash is faster and should use a shorter timeout.
605+
func TestProfileTimeout_FlashApplied(t *testing.T) {
606+
cfg := Config{
607+
APIKey: "sk-test",
608+
Model: "deepseek-v4-flash",
609+
}
610+
_, err := New(cfg)
611+
if err != nil {
612+
t.Fatalf("New() with Flash model should succeed: %v", err)
613+
}
614+
// Timeout is passed to the HTTP client internally; the key assertion
615+
// is that the agent is created successfully with the Flash profile.
616+
}
617+
618+
// TestKnownProfiles_FlashEntryIntegrity validates that the
619+
// deepseek-v4-flash entry in KnownProfiles has correct values
620+
// for every field.
621+
func TestKnownProfiles_FlashEntryIntegrity(t *testing.T) {
622+
var flashEntry *struct {
623+
Prefix string
624+
Profile ModelProfile
625+
}
626+
for _, entry := range KnownProfiles {
627+
if entry.Prefix == "deepseek-v4-flash" {
628+
flashEntry = &entry
629+
break
630+
}
631+
}
632+
if flashEntry == nil {
633+
t.Fatal("deepseek-v4-flash entry not found in KnownProfiles")
634+
}
635+
636+
if flashEntry.Prefix != "deepseek-v4-flash" {
637+
t.Errorf("Prefix = %q, want %q", flashEntry.Prefix, "deepseek-v4-flash")
638+
}
639+
if flashEntry.Profile.Label != "DeepSeek v4 Flash" {
640+
t.Errorf("Label = %q, want %q", flashEntry.Profile.Label, "DeepSeek v4 Flash")
641+
}
642+
if flashEntry.Profile.DefaultThinking != "" {
643+
t.Errorf("DefaultThinking = %q, want empty (Flash is faster without extended thinking)", flashEntry.Profile.DefaultThinking)
644+
}
645+
if flashEntry.Profile.Timeout != 90 {
646+
t.Errorf("Timeout = %d, want 90", flashEntry.Profile.Timeout)
647+
}
648+
if flashEntry.Profile.MaxContext != 131_072 {
649+
t.Errorf("MaxContext = %d, want 131_072", flashEntry.Profile.MaxContext)
650+
}
651+
}
652+
653+
// TestProfileLabel_Flash returns the human-readable label for Flash.
654+
func TestProfileLabel_Flash(t *testing.T) {
655+
if label := ProfileLabel("deepseek-v4-flash"); label != "DeepSeek v4 Flash" {
656+
t.Errorf("ProfileLabel = %q, want %q", label, "DeepSeek v4 Flash")
657+
}
658+
// Prefix match: deepseek-v4-flash-custom should also match
659+
if label := ProfileLabel("deepseek-v4-flash-experimental"); label != "DeepSeek v4 Flash" {
660+
t.Errorf("ProfileLabel for variant = %q, want %q", label, "DeepSeek v4 Flash")
661+
}
662+
}
663+
534664

535665
// ── Project File (AGENTS.md) Tests ───────────────────────────────────
536666

0 commit comments

Comments
 (0)