返回 DeepSeek-Reasonix
realcompat_test.go
根目录 / internal / provider / openai / realcompat_test.go
1 //go:build live
2
3 package openai
4
5 import (
6 "context"
7 "encoding/json"
8 "os"
9 "strings"
10 "testing"
11 "time"
12
13 "reasonix/internal/provider"
14 )
15
16 func TestRealLongCatToolReasoningReplay(t *testing.T) {
17 runLiveCompatibleToolReplay(t, liveCompatibleConfig{
18 env: "LONGCAT_API_KEY", name: "longcat", baseURL: "https://api.longcat.chat/openai/v1", model: "LongCat-2.0",
19 extra: map[string]any{"thinking": "enabled"},
20 })
21 }
22
23 func TestRealZhipuCodingPlanToolReasoningReplay(t *testing.T) {
24 runLiveCompatibleToolReplay(t, liveCompatibleConfig{
25 env: "ZHIPU_CODING_API_KEY", name: "zhipu-coding", baseURL: "https://api.z.ai/api/coding/paas/v4", model: "glm-5.1",
26 extra: map[string]any{"thinking": "enabled"},
27 })
28 }
29
30 func TestRealOpenCodeGoDeepSeekToolReasoningReplay(t *testing.T) {
31 runLiveCompatibleToolReplay(t, liveCompatibleConfig{
32 env: "OPENCODE_GO_API_KEY", name: "opencode-go", baseURL: "https://opencode.ai/zen/go/v1", model: "deepseek-v4-flash",
33 extra: map[string]any{"thinking": "enabled", "effort": "high"},
34 })
35 }
36
37 type liveCompatibleConfig struct {
38 env, name, baseURL, model string
39 extra map[string]any
40 }
41
42 func runLiveCompatibleToolReplay(t *testing.T, cfg liveCompatibleConfig) {
43 t.Helper()
44 key := os.Getenv(cfg.env)
45 if key == "" {
46 t.Skipf("%s not set — skipping live probe", cfg.env)
47 }
48 cfg.extra["api_key_env"] = cfg.env
49 p, err := New(provider.Config{Name: cfg.name, BaseURL: cfg.baseURL, Model: cfg.model, APIKey: key, Extra: cfg.extra})
50 if err != nil {
51 t.Fatalf("New: %v", err)
52 }
53 tools := []provider.ToolSchema{{
54 Name: "get_marker", Description: "Return a fixed integration-test marker. Always call this tool when asked for the marker.",
55 Parameters: json.RawMessage(`{"type":"object","properties":{},"additionalProperties":false}`),
56 }}
57 messages := []provider.Message{
58 {Role: provider.RoleSystem, Content: "You are a concise tool-using assistant. Call the requested tool before answering."},
59 {Role: provider.RoleUser, Content: "Call get_marker, then report its result."},
60 }
61 first := collectLiveCompatibleTurn(t, p, provider.Request{Messages: messages, Tools: tools, MaxTokens: 512})
62 if len(first.calls) == 0 {
63 t.Fatalf("first turn returned no tool call; text=%d reasoning=%d", len(first.text), len(first.reasoning))
64 }
65 messages = append(messages,
66 provider.Message{Role: provider.RoleAssistant, Content: first.text, ReasoningContent: first.reasoning, ToolCalls: first.calls},
67 provider.Message{Role: provider.RoleTool, ToolCallID: first.calls[0].ID, Name: first.calls[0].Name, Content: "protocol-round-trip-ok"},
68 )
69 second := collectLiveCompatibleTurn(t, p, provider.Request{Messages: messages, Tools: tools, MaxTokens: 512})
70 if strings.TrimSpace(second.text) == "" {
71 t.Fatalf("tool replay returned no visible answer; reasoning=%d calls=%d", len(second.reasoning), len(second.calls))
72 }
73 if first.promptTokens == 0 || second.promptTokens == 0 {
74 t.Fatalf("usage missing: first_prompt=%d second_prompt=%d", first.promptTokens, second.promptTokens)
75 }
76 t.Logf("%s tool replay: reasoning=%d calls=%d first_prompt=%d second_text=%d second_prompt=%d",
77 cfg.name, len(first.reasoning), len(first.calls), first.promptTokens, len(second.text), second.promptTokens)
78 }
79
80 type liveCompatibleTurn struct {
81 text, reasoning string
82 calls []provider.ToolCall
83 promptTokens int
84 }
85
86 func collectLiveCompatibleTurn(t *testing.T, p provider.Provider, req provider.Request) liveCompatibleTurn {
87 t.Helper()
88 ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
89 defer cancel()
90 ch, err := p.Stream(ctx, req)
91 if err != nil {
92 t.Fatalf("Stream: %v", err)
93 }
94 var out liveCompatibleTurn
95 var text, reasoning strings.Builder
96 for chunk := range ch {
97 switch chunk.Type {
98 case provider.ChunkText:
99 text.WriteString(chunk.Text)
100 case provider.ChunkReasoning:
101 reasoning.WriteString(chunk.Text)
102 case provider.ChunkToolCall:
103 if chunk.ToolCall != nil {
104 out.calls = append(out.calls, *chunk.ToolCall)
105 }
106 case provider.ChunkUsage:
107 if chunk.Usage != nil {
108 out.promptTokens = chunk.Usage.PromptTokens
109 }
110 case provider.ChunkError:
111 t.Fatalf("stream error: %v", chunk.Err)
112 }
113 }
114 out.text, out.reasoning = text.String(), reasoning.String()
115 return out
116 }
117
117 lines GO