Files
Agent-System-Backend/internal/tools/search.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

114 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package tools —— 网络搜索工具:桥接 Agent-Native 的 TavilyProvider
// (密钥池轮换由其实现)到统一注册表。配置了 tools.tavily.apiKeys 才注册
// (对齐 agent-native 语义:未配置不静默降级为空结果)。
package tools
import (
"context"
"encoding/json"
"fmt"
"strings"
agenttools "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/tools"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
// TavilySettings 装配期读取的搜索配置。
type TavilySettings struct {
Endpoint string
APIKeys []string
}
// RegisterSearchTools 注册 tavily_search(完整参数面)与 web_search(别名)。
// 工具面与 agent-native runtime/tools/search.go 逐字段对齐(名称/参数/输出/失败降级文案)。
func RegisterSearchTools(r *Registry, provider agenttools.SearchProvider) error {
return registerSearch(r, func(ToolExecCtx) agenttools.SearchProvider { return provider })
}
// RegisterLazySearchTools 注册按调用时取密钥的搜索工具:provider 在每次调用时
// 重建(设置页/svc 播种改了 tavily 密钥即刻生效,不再需要重启进程——
// 实测播种后 tavily 工具要重启才注册)。configure 返回 nil 时不静默降级,
// 返回明确的未配置提示。
func RegisterLazySearchTools(r *Registry, configure func() agenttools.SearchProvider) error {
return registerSearch(r, func(ToolExecCtx) agenttools.SearchProvider { return configure() })
}
func registerSearch(r *Registry, providerFor func(ToolExecCtx) agenttools.SearchProvider) error {
tavily := func(rc ToolExecCtx, args json.RawMessage) (string, error) {
provider := providerFor(rc)
if provider == nil {
return "", fmt.Errorf("搜索未配置:settings.tools.tavily.apiKeys(或对应 secret)为空;配置后立即生效,无需重启")
}
return execSearch(provider, args)
}
if err := r.Register(core.ToolDesc{
Name: "tavily_search",
Description: "Search the live internet for real-time information, competitor intelligence, regulatory news, product specs, or market data via Tavily (with automatic API key pool rotation).",
Source: core.SourceBuiltin,
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query keywords in Chinese or English"},
"search_depth": {"type": "string", "enum": ["basic", "advanced"], "description": "Search depth: 'basic' (fast summary) or 'advanced' (deep analysis, default 'basic')"},
"max_results": {"type": "number", "description": "Maximum number of search results to retrieve (1 to 10, default 5)"}
},
"required": ["query"]
}`),
}, tavily); err != nil {
return err
}
return r.Register(core.ToolDesc{
Name: "web_search",
Description: "Alias for tavily_search. Searches the live internet for information.",
Source: core.SourceBuiltin,
Parameters: json.RawMessage(`{
"type": "object",
"properties": {"query": {"type": "string", "description": "The search query keywords"}},
"required": ["query"]
}`),
}, tavily)
}
func execSearch(provider agenttools.SearchProvider, args json.RawMessage) (string, error) {
var params struct {
Query string `json:"query"`
SearchDepth string `json:"search_depth"`
MaxResults float64 `json:"max_results"`
}
if err := json.Unmarshal(args, &params); err != nil {
return "", err
}
query := strings.TrimSpace(params.Query)
if query == "" {
return "", fmt.Errorf("Error: query cannot be empty.")
}
resp, err := provider.Search(context.Background(), agenttools.SearchRequest{
Query: query,
Depth: params.SearchDepth,
MaxResults: int(params.MaxResults),
})
if err != nil {
// 失败对模型可见并指引回退(与 agent-native 扩展降级文案一致);
// 注册表把 err.Error() 作为输出回灌,故降级文案承载在 error 内。
return "", fmt.Errorf("[Tavily Search Failed]: %v\n(Agent should rely on workspace materials or local knowledge base as fallback.)", err)
}
return formatSearchResults(resp, query), nil
}
// formatSearchResults 输出 Markdown(对齐 agent-native 的呈现形态:答案 + 编号结果)。
func formatSearchResults(resp agenttools.SearchResponse, query string) string {
var sb strings.Builder
fmt.Fprintf(&sb, "## 🌐 Tavily 实时网络搜索结果: %q\n\n", query)
if strings.TrimSpace(resp.Answer) != "" {
fmt.Fprintf(&sb, "**概要**:%s\n\n", resp.Answer)
}
if len(resp.Results) == 0 {
sb.WriteString("(无命中结果)\n")
return sb.String()
}
for i, r := range resp.Results {
fmt.Fprintf(&sb, "%d. %s\n %s\n\n", i+1, r.Title, strings.TrimSpace(r.Content))
}
return sb.String()
}