返回 DeepSeek-Reasonix
live_official_continuity_test.go
根目录 / internal / agent / live_official_continuity_test.go
1 //go:build live
2
3 package agent
4
5 import (
6 "bytes"
7 "context"
8 "encoding/json"
9 "fmt"
10 "net/http/httptest"
11 "os"
12 "path/filepath"
13 "strings"
14 "sync/atomic"
15 "testing"
16 "time"
17
18 "reasonix/internal/event"
19 "reasonix/internal/tool"
20 )
21
22 // Six consecutive user turns reuse real reasoning and tool history. A save/load
23 // halfway through verifies that restart does not change healthy provider bytes.
24 func TestLiveOfficialConversationContinuity(t *testing.T) {
25 key := os.Getenv("DEEPSEEK_API_KEY")
26 if key == "" {
27 t.Skip("DEEPSEEK_API_KEY not set")
28 }
29 for _, model := range []string{"deepseek-v4-flash", "deepseek-v4-pro"} {
30 for _, protocol := range []string{"chat", "responses", "anthropic"} {
31 t.Run(model+"/"+protocol, func(t *testing.T) {
32 proxy := &officialRecoveryProxy{protocol: protocol, scenario: "continuity"}
33 srv := httptest.NewServer(proxy)
34 defer srv.Close()
35 p := officialMatrixProvider(t, key, model, protocol, "high", srv.URL)
36 reg := tool.NewRegistry()
37 var executions atomic.Int32
38 reg.Add(liveRecoveryEchoTool{executions: &executions})
39 sess := NewSession("Call echo exactly once for each new user request, then report its fixed marker. " + strings.Repeat("Keep completed work and history intact. ", 100))
40 sink := &recordSink{}
41 opts := Options{MaxSteps: 4, MaxOutputTokens: 2048, MissingReasoningWarnStateDir: t.TempDir()}
42 a := New(p, reg, sess, opts, sink)
43 path := filepath.Join(t.TempDir(), "session.jsonl")
44 lease, err := TryAcquireSessionLease(path)
45 if err != nil {
46 t.Fatal(err)
47 }
48 defer lease.Release()
49 for round := 1; round <= 6; round++ {
50 ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
51 err := a.Run(ctx, fmt.Sprintf("New request %d: call echo exactly once and report the marker. Earlier requests are complete.", round))
52 cancel()
53 if err != nil {
54 t.Fatalf("round %d: %v", round, err)
55 }
56 if executions.Load() != int32(round) {
57 t.Fatalf("round=%d total executions=%d", round, executions.Load())
58 }
59 if round == 3 {
60 if err := sess.Save(path); err != nil {
61 t.Fatal(err)
62 }
63 sess, err = LoadSession(path)
64 if err != nil {
65 t.Fatal(err)
66 }
67 a = New(p, reg, sess, opts, sink)
68 }
69 }
70 proxy.mu.Lock()
71 defer proxy.mu.Unlock()
72 if proxy.requests != 12 {
73 t.Fatalf("requests=%d want 12", proxy.requests)
74 }
75 var previous map[string]json.RawMessage
76 for n, body := range proxy.bodies {
77 var current map[string]json.RawMessage
78 if err := json.Unmarshal(body, &current); err != nil {
79 t.Fatal(err)
80 }
81 if n > 0 {
82 for _, field := range []string{"tools", "system", "model", "thinking", "reasoning", "output_config"} {
83 if !bytes.Equal(previous[field], current[field]) {
84 t.Fatalf("request %d changed %s", n+1, field)
85 }
86 }
87 field := "messages"
88 if protocol == "responses" {
89 field = "input"
90 }
91 var before, after []json.RawMessage
92 if err := json.Unmarshal(previous[field], &before); err != nil {
93 t.Fatal(err)
94 }
95 if err := json.Unmarshal(current[field], &after); err != nil {
96 t.Fatal(err)
97 }
98 if len(after) < len(before) {
99 t.Fatalf("request %d lost prefix", n+1)
100 }
101 for j := range before {
102 if !bytes.Equal(before[j], after[j]) {
103 t.Fatalf("request %d changed historical message %d", n+1, j)
104 }
105 }
106 }
107 previous = current
108 }
109 prompt, completion, hit, requests := 0, 0, 0, 0
110 for _, e := range sink.kinds(event.Usage) {
111 if u := e.Usage; u != nil {
112 prompt += u.PromptTokens
113 completion += u.CompletionTokens
114 hit += u.CacheHitTokens
115 requests += u.RequestCount
116 }
117 }
118 t.Logf("protocol=%s rounds=6 restarts=1 http_attempts=%d tool_executions=%d retries=%d prompt=%d completion=%d cache_hit=%d", protocol, requests, executions.Load(), len(sink.kinds(event.Retrying)), prompt, completion, hit)
119 })
120 }
121 }
122 }
123
123 lines GO