返回 DeepSeek-Reasonix
live_test.go
根目录 / internal / acp / live_test.go
1 //go:build live
2
3 // Live network end-to-end test, excluded from the normal suite by the `live`
4 // build tag. Run it against a real model with:
5 //
6 // set -a; . /path/to/.env; set +a
7 // go test -tags live -run Live ./internal/acp/ -v
8 //
9 // It drives the full ACP stack — acp.Serve → control.Controller → agent.Agent →
10 // the real OpenAI-compatible provider — over a tiny prompt, proving the live
11 // model path the hermetic tests stub out.
12 package acp
13
14 import (
15 "context"
16 "encoding/json"
17 "os"
18 "testing"
19 "time"
20
21 "reasonix/internal/agent"
22 "reasonix/internal/control"
23 "reasonix/internal/provider"
24 _ "reasonix/internal/provider/openai" // registers the "openai" provider kind
25 "reasonix/internal/tool"
26 )
27
28 type liveFactory struct{ prov provider.Provider }
29
30 func (f *liveFactory) NewSession(_ context.Context, p SessionParams) (*control.Controller, error) {
31 executor := agent.New(f.prov, tool.NewRegistry(),
32 agent.NewSession("You are a terse assistant. Answer in as few words as possible."),
33 agent.Options{MaxSteps: 3}, p.Sink)
34 return control.New(control.Options{Runner: executor, Executor: executor, Sink: p.Sink, Label: "deepseek"}), nil
35 }
36
37 func TestLiveDeepSeekPrompt(t *testing.T) {
38 key := os.Getenv("DEEPSEEK_API_KEY")
39 if key == "" {
40 t.Skip("DEEPSEEK_API_KEY not set")
41 }
42 prov, err := provider.New("openai", provider.Config{
43 Name: "deepseek",
44 BaseURL: "https://api.deepseek.com",
45 Model: "deepseek-v4-flash",
46 APIKey: key,
47 })
48 if err != nil {
49 t.Fatalf("provider.New: %v", err)
50 }
51
52 client, stop := startServer(t, &liveFactory{prov: prov})
53 defer stop()
54
55 client.call(t, "initialize", InitializeParams{ProtocolVersion: 1})
56 resp := client.call(t, "session/new", SessionNewParams{})
57 var nr SessionNewResult
58 if err := json.Unmarshal(resp.Result, &nr); err != nil {
59 t.Fatalf("session/new: %v", err)
60 }
61
62 promptCh := client.callAsync("session/prompt", SessionPromptParams{
63 SessionID: nr.SessionID,
64 Prompt: []ContentBlock{{Type: "text", Text: "Reply with exactly one word: hi"}},
65 })
66
67 // Collect updates until the prompt response arrives (network: allow up to 60s).
68 var notifs []frame
69 var pResp frame
70 deadline := time.After(60 * time.Second)
71 collect:
72 for {
73 select {
74 case f := <-client.notifs:
75 notifs = append(notifs, f)
76 case pResp = <-promptCh:
77 break collect
78 case <-deadline:
79 t.Fatal("live prompt timed out after 60s")
80 }
81 }
82
83 var text string
84 for _, n := range notifs {
85 if updateKind(t, n) != "agent_message_chunk" {
86 continue
87 }
88 var p struct {
89 Update struct {
90 Content struct {
91 Text string `json:"text"`
92 } `json:"content"`
93 } `json:"update"`
94 }
95 json.Unmarshal(n.Params, &p)
96 text += p.Update.Content.Text
97 }
98 if text == "" {
99 t.Fatal("no agent_message_chunk text received from the live model")
100 }
101
102 var pr SessionPromptResult
103 if err := json.Unmarshal(pResp.Result, &pr); err != nil {
104 t.Fatalf("prompt result: %v", err)
105 }
106 if pr.StopReason != StopEndTurn {
107 t.Errorf("stopReason = %q, want end_turn", pr.StopReason)
108 }
109 t.Logf("live model replied: %q (stopReason=%s)", text, pr.StopReason)
110 }
111
111 lines GO