| 1 | //go:build live |
| 2 | |
| 3 | package websearch |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "encoding/json" |
| 8 | "os" |
| 9 | "testing" |
| 10 | |
| 11 | "reasonix/internal/config" |
| 12 | "reasonix/internal/provider" |
| 13 | _ "reasonix/internal/provider/anthropic" |
| 14 | ) |
| 15 | |
| 16 | // Explicitly opt in: one bounded Flash search on the canonical official search wire. |
| 17 | // Credentials remain in memory; only result counts and usage are logged. |
| 18 | func TestLiveIndependentWebSearch(t *testing.T) { |
| 19 | if os.Getenv("REASONIX_LIVE_WEB_SEARCH") != "1" { |
| 20 | t.Skip("set REASONIX_LIVE_WEB_SEARCH=1 to run the bounded live probe") |
| 21 | } |
| 22 | key := os.Getenv("DEEPSEEK_API_KEY") |
| 23 | if key == "" { |
| 24 | t.Skip("no official DeepSeek credential available") |
| 25 | } |
| 26 | entry := &config.ProviderEntry{Name: "deepseek", Kind: "responses", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-flash", APIKeyEnv: "DEEPSEEK_API_KEY"} |
| 27 | entry.ResolveAPIKeyFromProcessEnvForProbe() |
| 28 | // Pin only this process credential; do not read or write global credentials. |
| 29 | route := (&config.Config{}).ResolveWebSearchProvider(entry) |
| 30 | if route == nil { |
| 31 | t.Fatal("official search route was not resolved") |
| 32 | } |
| 33 | if route.Kind != "anthropic" { |
| 34 | t.Fatalf("unexpected official search wire: %s", route.Kind) |
| 35 | } |
| 36 | tool := &Tool{Factory: func() (provider.Provider, error) { |
| 37 | return provider.New(route.Kind, provider.Config{Name: route.Name, BaseURL: route.BaseURL, Model: route.Model, APIKey: key, Extra: map[string]any{"web_search": true, "reject_redirects": true, "thinking": "enabled", "effort": "low"}}) |
| 38 | }, ReportUsage: func(u *provider.Usage) { |
| 39 | t.Logf("prompt=%d completion=%d requests=%d", u.PromptTokens, u.CompletionTokens, u.RequestCount) |
| 40 | }} |
| 41 | output, err := tool.Execute(context.Background(), json.RawMessage(`{"query":"Find the official DeepSeek API thinking mode documentation and summarize its reasoning passback requirement with a source URL."}`)) |
| 42 | if err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | var result Result |
| 46 | if err := json.Unmarshal([]byte(output), &result); err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | if len(result.Sources) == 0 || result.Summary == "" { |
| 50 | t.Fatal("search did not return a summary and structured sources") |
| 51 | } |
| 52 | t.Logf("sources=%d summary_bytes=%d", len(result.Sources), len(result.Summary)) |
| 53 | } |
| 54 |