mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 20:57:21 +08:00
### Summary
As title
- [x] fix tool & component `Retrieval KB`
- [x] fix agent cannot use `Retrieval KB` in agent chat
#### Main cause
Model provider do not set up:
```Go
if chatModelConfig.Tools != nil {
reqBody["tools"] = chatModelConfig.Tools
}
```
#### Working Now
<img width="3774" height="2128" alt="image"
src="https://github.com/user-attachments/assets/400a349d-0211-43e5-a7ec-7a014acf77a6"
/>
```
____________________________________
< This PR takes me all day to do it. >
------------------------------------
\
\ (\__/)
(•ㅅ•)
/ づ
```
717 lines
23 KiB
Go
717 lines
23 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package tool
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
einotool "github.com/cloudwego/eino/components/tool"
|
|
)
|
|
|
|
// Factory builds a tool instance by DSL / Agent-visible name and
|
|
// optional node-level configuration. The config map belongs to the
|
|
// Agent node / DSL, not to the model-emitted function-call args.
|
|
type Factory func(params map[string]any) (einotool.BaseTool, error)
|
|
|
|
var registry = map[string]Factory{
|
|
"akshare": buildAkShareTool,
|
|
"arxiv": buildArxivTool,
|
|
"bgpt": buildBGPTTool,
|
|
"code_exec": noConfig("code_exec", func() einotool.BaseTool { return NewCodeExecTool() }),
|
|
"crawler": noConfig("crawler", func() einotool.BaseTool { return NewCrawlerTool() }),
|
|
"deepl": noConfig("deepl", func() einotool.BaseTool { return NewDeepLTool() }),
|
|
"duckduckgo": buildDuckDuckGoTool,
|
|
"email": buildEmailTool,
|
|
"execute_sql": buildExeSQLTool,
|
|
"exesql": buildExeSQLTool,
|
|
"github": buildGitHubTool,
|
|
"google": buildGoogleTool,
|
|
"google_scholar": buildGoogleScholarTool,
|
|
"google_scholar_search": buildGoogleScholarTool,
|
|
"jin10": noConfig("jin10", func() einotool.BaseTool { return NewJin10Tool() }),
|
|
"keenable": buildKeenableTool,
|
|
"pubmed": buildPubMedTool,
|
|
"qweather": noConfig("qweather", func() einotool.BaseTool { return NewQWeatherTool() }),
|
|
"retrieval": buildRetrievalTool,
|
|
"search_my_dataset": buildRetrievalTool,
|
|
"search_my_dateset": buildRetrievalTool,
|
|
"searxng": buildSearXNGTool,
|
|
"tavily": buildTavilyTool,
|
|
"tavily_extract": buildTavilyExtractTool,
|
|
"tushare": noConfig("tushare", func() einotool.BaseTool { return NewTushareTool() }),
|
|
"wencai": buildWencaiTool,
|
|
"web_crawler": noConfig("web_crawler", func() einotool.BaseTool { return NewCrawlerTool() }),
|
|
"wikipedia": buildWikipediaTool,
|
|
"wikipedia_search": buildWikipediaTool,
|
|
"yahoo_finance": buildYahooFinanceTool,
|
|
}
|
|
|
|
func noConfig(name string, fn func() einotool.BaseTool) Factory {
|
|
return func(params map[string]any) (einotool.BaseTool, error) {
|
|
if len(params) != 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q does not accept node-level params", name)
|
|
}
|
|
return fn(), nil
|
|
}
|
|
}
|
|
|
|
// BuildByName resolves a tool name into an Eino BaseTool.
|
|
func BuildByName(name string, params map[string]any) (einotool.BaseTool, error) {
|
|
key := strings.ToLower(strings.TrimSpace(name))
|
|
if key == "" {
|
|
return nil, fmt.Errorf("agent tool: empty tool name")
|
|
}
|
|
factory, ok := registry[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("agent tool: unsupported tool %q", name)
|
|
}
|
|
if factory == nil {
|
|
return nil, fmt.Errorf("agent tool: nil factory for %q", name)
|
|
}
|
|
return factory(params)
|
|
}
|
|
|
|
// BuildAll resolves a list of tool names into Eino BaseTool instances.
|
|
// perToolParams is keyed by the Agent-visible tool name.
|
|
func BuildAll(names []string, perToolParams map[string]map[string]any) ([]einotool.BaseTool, error) {
|
|
if len(names) == 0 {
|
|
return nil, nil
|
|
}
|
|
tools := make([]einotool.BaseTool, 0, len(names))
|
|
for _, name := range names {
|
|
var params map[string]any
|
|
if perToolParams != nil {
|
|
params = perToolParams[strings.ToLower(strings.TrimSpace(name))]
|
|
if params == nil {
|
|
params = perToolParams[name]
|
|
}
|
|
}
|
|
t, err := BuildByName(name, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tools = append(tools, t)
|
|
}
|
|
return tools, nil
|
|
}
|
|
|
|
func buildAkShareTool(params map[string]any) (einotool.BaseTool, error) {
|
|
topN := defaultAkShareTopN
|
|
if len(params) != 0 {
|
|
for key := range params {
|
|
if key != "top_n" {
|
|
return nil, fmt.Errorf("agent tool: tool %q only accepts node-level param top_n", "akshare")
|
|
}
|
|
}
|
|
if v, ok := intParam(params, "top_n"); ok {
|
|
topN = v
|
|
}
|
|
if topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "akshare")
|
|
}
|
|
}
|
|
return NewAkShareToolWithTopN(nil, topN), nil
|
|
}
|
|
|
|
func buildArxivTool(params map[string]any) (einotool.BaseTool, error) {
|
|
topN := defaultArxivTopN
|
|
sortBy := defaultArxivSortBy
|
|
if v, ok := intParam(params, "top_n"); ok {
|
|
topN = v
|
|
}
|
|
if topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "arxiv")
|
|
}
|
|
if v, ok := stringParam(params, "sort_by"); ok {
|
|
sortBy = v
|
|
}
|
|
if !ArxivSortBySupported(sortBy) {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported sort_by %q", "arxiv", sortBy)
|
|
}
|
|
return NewArxivToolWithParams(nil, topN, sortBy), nil
|
|
}
|
|
|
|
func buildBGPTTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := bgptParams{}
|
|
if value, ok := params["api_key"]; ok {
|
|
apiKey, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param api_key", "bgpt")
|
|
}
|
|
defaults.APIKey = apiKey
|
|
}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if text, isString := value.(string); isString {
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(text))
|
|
topN, valid = parsed, err == nil
|
|
}
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "bgpt")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["days_back"]; ok && value != nil && value != "" {
|
|
daysBack, valid := strictInt(value)
|
|
if !valid || daysBack <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param days_back", "bgpt")
|
|
}
|
|
defaults.DaysBack = daysBack
|
|
}
|
|
return newBGPTTool(nil, defaults), nil
|
|
}
|
|
|
|
func buildExeSQLTool(params map[string]any) (einotool.BaseTool, error) {
|
|
conn, err := decodeExeSQLConnParams(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewExeSQLTool(conn), nil
|
|
}
|
|
|
|
func buildEmailTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := emailParams{SMTPPort: 465}
|
|
stringFields := map[string]*string{
|
|
"smtp_server": &defaults.SMTPServer,
|
|
"email": &defaults.Email,
|
|
"smtp_username": &defaults.SMTPUsername,
|
|
"password": &defaults.Password,
|
|
"sender_name": &defaults.SenderName,
|
|
"to_email": &defaults.ToEmail,
|
|
"cc_email": &defaults.CCEmail,
|
|
"content": &defaults.Content,
|
|
"subject": &defaults.Subject,
|
|
}
|
|
for key, destination := range stringFields {
|
|
value, exists := params[key]
|
|
if !exists {
|
|
continue
|
|
}
|
|
text, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param %s", "email", key)
|
|
}
|
|
*destination = text
|
|
}
|
|
if value, exists := params["smtp_port"]; exists {
|
|
port, valid := strictInt(value)
|
|
if text, isString := value.(string); isString {
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(text))
|
|
port, valid = parsed, err == nil
|
|
}
|
|
if !valid || port <= 0 || port > 65535 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires integer node-level param smtp_port in [1, 65535]", "email")
|
|
}
|
|
defaults.SMTPPort = port
|
|
}
|
|
return newEmailTool(defaults), nil
|
|
}
|
|
|
|
func buildDuckDuckGoTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := duckduckgoParams{}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "duckduckgo")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["channel"]; ok {
|
|
channel, valid := value.(string)
|
|
if !valid || (channel != "text" && channel != "general" && channel != "news") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported channel %q", "duckduckgo", channel)
|
|
}
|
|
defaults.Channel = normalizeDuckDuckGoChannel(channel)
|
|
}
|
|
return newDuckDuckGoTool(nil, defaults), nil
|
|
}
|
|
|
|
func buildGoogleTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := googleParams{}
|
|
if v, ok := stringParam(params, "api_key"); ok {
|
|
defaults.APIKey = v
|
|
}
|
|
if v, ok := stringParam(params, "country"); ok {
|
|
defaults.Country = v
|
|
}
|
|
if v, ok := stringParam(params, "language"); ok {
|
|
defaults.Language = v
|
|
}
|
|
if v, ok := stringParam(params, "q"); ok {
|
|
defaults.Q = v
|
|
}
|
|
if v, ok := intParam(params, "start"); ok {
|
|
defaults.Start = v
|
|
}
|
|
if v, ok := intParam(params, "num"); ok {
|
|
defaults.Num = v
|
|
}
|
|
return NewGoogleToolWithDefaults(nil, defaults), nil
|
|
}
|
|
|
|
func buildGitHubTool(params map[string]any) (einotool.BaseTool, error) {
|
|
topN := defaultGitHubTopN
|
|
if raw, exists := params["top_n"]; exists {
|
|
value, ok := intParam(params, "top_n")
|
|
if !ok {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "github")
|
|
}
|
|
if decimal, ok := raw.(float64); ok && math.Trunc(decimal) != decimal {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "github")
|
|
}
|
|
topN = value
|
|
}
|
|
if topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "github")
|
|
}
|
|
if topN > maxGitHubTopN {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires node-level param top_n to be at most %d", "github", maxGitHubTopN)
|
|
}
|
|
return NewGitHubToolWithDefaults(nil, githubParams{TopN: topN}), nil
|
|
}
|
|
|
|
func buildGoogleScholarTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := googleScholarParams{}
|
|
if v, ok := stringParam(params, "query"); ok {
|
|
defaults.Query = v
|
|
}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "google_scholar")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["sort_by"]; ok {
|
|
sortBy, valid := value.(string)
|
|
if !valid || (sortBy != "date" && sortBy != "relevance") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported sort_by %q", "google_scholar", sortBy)
|
|
}
|
|
defaults.SortBy = sortBy
|
|
}
|
|
if value, ok := params["year_low"]; ok && value != nil {
|
|
yearLow, valid := strictInt(value)
|
|
if !valid || yearLow <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param year_low", "google_scholar")
|
|
}
|
|
defaults.YearLow = yearLow
|
|
}
|
|
if value, ok := params["year_high"]; ok && value != nil {
|
|
yearHigh, valid := strictInt(value)
|
|
if !valid || yearHigh <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param year_high", "google_scholar")
|
|
}
|
|
defaults.YearHigh = yearHigh
|
|
}
|
|
if v, ok := boolParam(params, "patents"); ok {
|
|
defaults.Patents = &v
|
|
}
|
|
if value, ok := params["patents"]; ok {
|
|
if _, valid := value.(bool); !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires boolean node-level param patents", "google_scholar")
|
|
}
|
|
}
|
|
return NewGoogleScholarToolWithDefaults(nil, defaults), nil
|
|
}
|
|
|
|
func buildPubMedTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := pubmedParams{}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "pubmed")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["email"]; ok {
|
|
email, valid := value.(string)
|
|
if !valid || strings.TrimSpace(email) == "" {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires non-empty string node-level param email", "pubmed")
|
|
}
|
|
defaults.Email = email
|
|
}
|
|
return NewPubMedToolWithDefaults(nil, defaults), nil
|
|
}
|
|
|
|
func buildRetrievalTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := retrievalArgs{}
|
|
for key := range params {
|
|
switch key {
|
|
case "dataset_ids", "kb_ids", "top_n", "top_k", "similarity_threshold",
|
|
"keywords_similarity_weight", "use_kg", "rerank_id", "empty_response",
|
|
"toc_enhance", "meta_data_filter", "retrieval_from", "memory_ids",
|
|
"kb_vars", "cross_languages", "function_name", "description", "meta",
|
|
"inputs", "outputs":
|
|
default:
|
|
return nil, fmt.Errorf("agent tool: retrieval tool does not accept node-level param %s", key)
|
|
}
|
|
}
|
|
|
|
if ids, ok, err := stringSliceParam(params, "dataset_ids"); err != nil {
|
|
return nil, fmt.Errorf("agent tool: retrieval config: %w", err)
|
|
} else if ok {
|
|
defaults.DatasetIDs = ids
|
|
}
|
|
if ids, ok, err := stringSliceParam(params, "kb_ids"); err != nil {
|
|
return nil, fmt.Errorf("agent tool: retrieval config: %w", err)
|
|
} else if ok {
|
|
defaults.KBIDs = ids
|
|
if len(defaults.DatasetIDs) == 0 {
|
|
defaults.DatasetIDs = ids
|
|
}
|
|
}
|
|
if v, ok := intParam(params, "top_n"); ok {
|
|
defaults.TopN = v
|
|
}
|
|
if raw, exists := params["top_k"]; exists {
|
|
value, ok := strictInt(raw)
|
|
if !ok || value <= 0 {
|
|
return nil, fmt.Errorf("agent tool: retrieval tool requires positive integer node-level param top_k")
|
|
}
|
|
defaults.TopK = value
|
|
}
|
|
if v, ok := boolParam(params, "use_kg"); ok {
|
|
defaults.UseKG = v
|
|
}
|
|
if v, ok := floatParam(params, "similarity_threshold"); ok {
|
|
defaults.SimilarityThreshold = v
|
|
}
|
|
if v, ok := floatParam(params, "keywords_similarity_weight"); ok {
|
|
if v < 0 || v > 1 {
|
|
return nil, fmt.Errorf("agent tool: retrieval tool requires node-level param keywords_similarity_weight in [0,1]")
|
|
}
|
|
defaults.KeywordsSimilarityWeight = &v
|
|
}
|
|
return NewRetrievalToolWithDefaults(defaults), nil
|
|
}
|
|
|
|
func buildSearXNGTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := defaultSearXNGParams()
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := parseSearXNGTopN(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "searxng")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["searxng_url"]; ok {
|
|
searxngURL, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param searxng_url", "searxng")
|
|
}
|
|
defaults.SearXNGURL = searxngURL
|
|
}
|
|
return newSearXNGToolWithDefaults(nil, defaults), nil
|
|
}
|
|
|
|
func buildWencaiTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := wencaiParams{}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "wencai")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["query_type"]; ok {
|
|
queryType, valid := value.(string)
|
|
if !valid || !isWencaiQueryTypeSupported(queryType) {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported query_type %q", "wencai", queryType)
|
|
}
|
|
defaults.QueryType = queryType
|
|
}
|
|
return newWencaiTool(defaults), nil
|
|
}
|
|
|
|
func buildTavilyExtractTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := tavilyExtractParams{}
|
|
if value, ok := params["api_key"]; ok {
|
|
apiKey, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param api_key", "tavily_extract")
|
|
}
|
|
defaults.APIKey = apiKey
|
|
}
|
|
if value, ok := params["urls"]; ok {
|
|
defaults.URLs = value
|
|
}
|
|
if value, ok := params["extract_depth"]; ok {
|
|
extractDepth, valid := value.(string)
|
|
if !valid || (extractDepth != "basic" && extractDepth != "advanced") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported extract_depth %q", "tavily_extract", extractDepth)
|
|
}
|
|
defaults.ExtractDepth = extractDepth
|
|
}
|
|
if value, ok := params["format"]; ok {
|
|
format, valid := value.(string)
|
|
if !valid || (format != "markdown" && format != "text") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported format %q", "tavily_extract", format)
|
|
}
|
|
defaults.Format = format
|
|
}
|
|
return newTavilyExtractTool(nil, nil, defaults), nil
|
|
}
|
|
|
|
func buildTavilyTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := tavilyParams{}
|
|
if value, ok := params["api_key"]; ok {
|
|
apiKey, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param api_key", "tavily")
|
|
}
|
|
defaults.APIKey = apiKey
|
|
}
|
|
if value, ok := params["search_depth"]; ok {
|
|
searchDepth, valid := value.(string)
|
|
if !valid || (searchDepth != "basic" && searchDepth != "advanced") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported search_depth %q", "tavily", searchDepth)
|
|
}
|
|
defaults.SearchDepth = searchDepth
|
|
}
|
|
if value, ok := params["max_results"]; ok {
|
|
maxResults, valid := strictInt(value)
|
|
if !valid || maxResults <= 0 || maxResults > 20 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires integer node-level param max_results within [1, 20]", "tavily")
|
|
}
|
|
defaults.MaxResults = maxResults
|
|
}
|
|
if value, ok := params["days"]; ok {
|
|
days, valid := strictInt(value)
|
|
if !valid || days <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param days", "tavily")
|
|
}
|
|
defaults.Days = days
|
|
}
|
|
for _, key := range []string{"include_answer", "include_raw_content", "include_images", "include_image_descriptions"} {
|
|
value, ok := params[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
flag, valid := value.(bool)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires boolean node-level param %s", "tavily", key)
|
|
}
|
|
switch key {
|
|
case "include_answer":
|
|
defaults.IncludeAnswer = flag
|
|
case "include_raw_content":
|
|
defaults.IncludeRawContent = flag
|
|
case "include_images":
|
|
defaults.IncludeImages = flag
|
|
case "include_image_descriptions":
|
|
defaults.IncludeImageDescriptions = flag
|
|
}
|
|
}
|
|
return newTavilyTool(nil, nil, defaults), nil
|
|
}
|
|
|
|
func buildKeenableTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := keenableParams{}
|
|
apiKey := ""
|
|
if value, ok := params["api_key"]; ok {
|
|
var valid bool
|
|
apiKey, valid = value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param api_key", "keenable")
|
|
}
|
|
}
|
|
if value, ok := params["mode"]; ok {
|
|
mode, valid := value.(string)
|
|
if !valid || (mode != "pro" && mode != "realtime") {
|
|
return nil, fmt.Errorf("agent tool: tool %q has unsupported mode %q", "keenable", mode)
|
|
}
|
|
defaults.Mode = mode
|
|
}
|
|
if value, ok := params["top_n"]; ok {
|
|
topN, valid := strictInt(value)
|
|
if !valid || topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "keenable")
|
|
}
|
|
defaults.TopN = topN
|
|
}
|
|
if value, ok := params["site"]; ok {
|
|
site, valid := value.(string)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires string node-level param site", "keenable")
|
|
}
|
|
defaults.Site = site
|
|
}
|
|
if defaults.Mode == "realtime" && strings.TrimSpace(apiKey) == "" {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires api_key for realtime mode", "keenable")
|
|
}
|
|
return newKeenableTool(nil, nil, apiKey, defaults), nil
|
|
}
|
|
|
|
func buildWikipediaTool(params map[string]any) (einotool.BaseTool, error) {
|
|
topN := defaultWikipediaTopN
|
|
language := defaultWikipediaLanguage
|
|
if v, ok := intParam(params, "top_n"); ok {
|
|
topN = v
|
|
}
|
|
if topN <= 0 {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires positive integer node-level param top_n", "wikipedia")
|
|
}
|
|
if v, ok := stringParam(params, "language"); ok {
|
|
language = strings.TrimSpace(v)
|
|
}
|
|
if language == "" {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires non-empty string node-level param language", "wikipedia")
|
|
}
|
|
if !WikipediaLanguageSupported(language) {
|
|
return nil, fmt.Errorf("agent tool: tool %q unsupported node-level param language %q", "wikipedia", language)
|
|
}
|
|
return NewWikipediaToolWithParams(nil, topN, language), nil
|
|
}
|
|
|
|
func buildYahooFinanceTool(params map[string]any) (einotool.BaseTool, error) {
|
|
defaults := defaultYahooFinanceParams()
|
|
if value, exists := params["info"]; exists {
|
|
flag, valid := value.(bool)
|
|
if !valid {
|
|
return nil, fmt.Errorf("agent tool: tool %q requires boolean node-level param info", "yahoo_finance")
|
|
}
|
|
defaults.Info = flag
|
|
}
|
|
return NewYahooFinanceToolWithDefaults(nil, defaults), nil
|
|
}
|
|
|
|
func decodeExeSQLConnParams(params map[string]any) (exesqlConnParams, error) {
|
|
if len(params) == 0 {
|
|
return exesqlConnParams{}, fmt.Errorf(
|
|
"agent tool: execute_sql requires node-level params " +
|
|
"(db_type/host/port/database/username/password)",
|
|
)
|
|
}
|
|
conn, err := NewExeSQLConnParams(params)
|
|
if err != nil {
|
|
return exesqlConnParams{}, fmt.Errorf("agent tool: execute_sql config: %w", err)
|
|
}
|
|
if err := conn.check(); err != nil {
|
|
return exesqlConnParams{}, fmt.Errorf("agent tool: execute_sql config: %w", err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
func stringParam(params map[string]any, key string) (string, bool) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
s, ok := v.(string)
|
|
return s, ok
|
|
}
|
|
|
|
func intParam(params map[string]any, key string) (int, bool) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch x := v.(type) {
|
|
case int:
|
|
return x, true
|
|
case int32:
|
|
return int(x), true
|
|
case int64:
|
|
return int(x), true
|
|
case float64:
|
|
return int(x), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func floatParam(params map[string]any, key string) (float64, bool) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch x := v.(type) {
|
|
case float64:
|
|
return x, true
|
|
case float32:
|
|
return float64(x), true
|
|
case int:
|
|
return float64(x), true
|
|
case int32:
|
|
return float64(x), true
|
|
case int64:
|
|
return float64(x), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func stringSliceParam(params map[string]any, key string) ([]string, bool, error) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
switch x := v.(type) {
|
|
case []string:
|
|
return append([]string(nil), x...), true, nil
|
|
case []any:
|
|
out := make([]string, 0, len(x))
|
|
for _, item := range x {
|
|
s, ok := item.(string)
|
|
if !ok {
|
|
return nil, true, fmt.Errorf("%s must be a string list", key)
|
|
}
|
|
if strings.TrimSpace(s) != "" {
|
|
out = append(out, strings.TrimSpace(s))
|
|
}
|
|
}
|
|
return out, true, nil
|
|
default:
|
|
return nil, true, fmt.Errorf("%s must be a string list", key)
|
|
}
|
|
}
|
|
|
|
func strictInt(value any) (int, bool) {
|
|
switch x := value.(type) {
|
|
case int:
|
|
return x, true
|
|
case int32:
|
|
return int(x), true
|
|
case int64:
|
|
if int64(int(x)) != x {
|
|
return 0, false
|
|
}
|
|
return int(x), true
|
|
case float64:
|
|
maxInt := int(^uint(0) >> 1)
|
|
minInt := -maxInt - 1
|
|
if math.Trunc(x) != x || x >= float64(maxInt) || x <= float64(minInt) {
|
|
return 0, false
|
|
}
|
|
return int(x), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func boolParam(params map[string]any, key string) (bool, bool) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
b, ok := v.(bool)
|
|
return b, ok
|
|
}
|