Files
Agent-System-Backend/internal/tools/search_test.go
T
weichen 0d55ebb948 Wire Tavily web search through the prompt-backed tool face
Why: Agent-Native ships a Tavily integration (key-pool rotation,
markdown output, fallback copy) that the backend never registered, so
the catalog's WebSearch prompt had no carrier; wiring the tool gives it
one and gives the agent live-web reach.

Evidence: 4 new tool tests (registration, markdown formatting, empty
query, failure fallback carried inside the error to match registry
replay semantics); go test ./... 16 packages ok; live boot check - no
keys means not registered, two keys log registration with both tools
in /api/tools and 46 library modules in /api/prompts.

Impact: tools.tavily.apiKeys/endpoint settings (restart effect; empty
pool means the tool is absent, no silent degradation); tool.tavily_search
and tool.web_search prompt modules inject citation discipline (cite
sources, keep inference separate, fall back to workspace materials on
failure); library floor raised to 46.

Refs: feature/tavily-search-tool task card; guide disposition table in
Documents.
2026-09-30 11:00:11 +00:00

86 lines
2.5 KiB
Go

package tools
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
agenttools "agent-native/runtime/tools"
core "agent-system-core"
)
type fakeProvider struct {
resp agenttools.SearchResponse
err error
}
func (f *fakeProvider) Search(ctx context.Context, req agenttools.SearchRequest) (agenttools.SearchResponse, error) {
return f.resp, f.err
}
func newSearchRegistry(t *testing.T, p agenttools.SearchProvider) *Registry {
t.Helper()
reg := NewRegistry()
if err := reg.RegisterBuiltin(); err != nil {
t.Fatal(err)
}
if err := RegisterSearchTools(reg, p); err != nil {
t.Fatal(err)
}
return reg
}
func TestSearchToolsRegistered(t *testing.T) {
reg := newSearchRegistry(t, &fakeProvider{})
for _, name := range []string{"tavily_search", "web_search"} {
if _, _, ok := reg.Get(name); !ok {
t.Errorf("tool %s not registered", name)
}
}
}
func execSearchTool(t *testing.T, reg *Registry, name, args string) core.ToolCallResult {
t.Helper()
return reg.Execute(nil, core.ToolCallRequest{CallID: "c1", Name: name, Arguments: json.RawMessage(args)})
}
func TestSearchExecuteFormatsResults(t *testing.T) {
reg := newSearchRegistry(t, &fakeProvider{resp: agenttools.SearchResponse{
Answer: "概要答案",
Results: []agenttools.SearchResult{
{Title: "结果一", Content: "内容甲", Score: 0.9},
{Title: "结果二", Content: "内容乙", Score: 0.8},
},
}})
res := execSearchTool(t, reg, "tavily_search", `{"query":"超声 法规","search_depth":"basic","max_results":5}`)
if res.Status != "ok" {
t.Fatalf("status=%s output=%q", res.Status, res.Output)
}
for _, want := range []string{"Tavily 实时网络搜索结果", "概要答案", "结果一", "内容乙"} {
if !strings.Contains(res.Output, want) {
t.Errorf("output missing %q:\n%s", want, res.Output)
}
}
}
func TestSearchEmptyQuery(t *testing.T) {
reg := newSearchRegistry(t, &fakeProvider{})
res := execSearchTool(t, reg, "web_search", `{"query":" "}`)
if res.Status == "ok" || !strings.Contains(res.Output, "query cannot be empty") {
t.Errorf("empty query: status=%s out=%q", res.Status, res.Output)
}
}
func TestSearchFailureFallback(t *testing.T) {
reg := newSearchRegistry(t, &fakeProvider{err: errors.New("all keys failed")})
res := execSearchTool(t, reg, "tavily_search", `{"query":"q"}`)
if res.Status == "ok" {
t.Error("provider failure should surface as tool error status")
}
if !strings.Contains(res.Output, "[Tavily Search Failed]") || !strings.Contains(res.Output, "fallback") {
t.Errorf("fallback copy missing: %q", res.Output)
}
}