feat(plugins): implement web-search, web-fetch, http and file tools
- web-search: real SearXNG /search?format=json query, top-5 formatted results (was a 'not configured' stub) - web-fetch: fetch page, strip scripts/styles/tags, truncate to 4000 runes for the LLM context - http: generic request tool (GET/POST/PUT/DELETE/PATCH) with scheme and method validation, status + truncated body - file: sandboxed read/write/list inside the data directory with path escape rejection; split into file_read/file_write/file_list - all tool schemas converted to proper JSON Schema; httptest/tempdir tests for each plugin
This commit is contained in:
1 parent
d80710d85a
commit
9d040fde0b
8 files changed
+599
-44
No files matched your search
@@ -1,19 +1,91 @@
|
||||
package websearch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
|
||||
)
|
||||
type Plugin struct{ searxngURL string }
|
||||
func New(url string) *Plugin { return &Plugin{searxngURL: url} }
|
||||
func (p *Plugin) Name() string { return "web-search" }
|
||||
func (p *Plugin) Version() string { return "1.0.0" }
|
||||
|
||||
type Plugin struct {
|
||||
searxngURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(url string) *Plugin {
|
||||
return &Plugin{searxngURL: strings.TrimRight(url, "/"), client: &http.Client{Timeout: 15 * time.Second}}
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return "web-search" }
|
||||
func (p *Plugin) Version() string { return "1.0.0" }
|
||||
func (p *Plugin) Description() string { return "网络搜索" }
|
||||
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
|
||||
func (p *Plugin) Close() error { return nil }
|
||||
func (p *Plugin) Enabled() bool { return p.searxngURL != "" }
|
||||
func (p *Plugin) Close() error { return nil }
|
||||
func (p *Plugin) Enabled() bool { return p.searxngURL != "" }
|
||||
|
||||
func (p *Plugin) Tools() []plugin.ToolDefinition {
|
||||
return []plugin.ToolDefinition{{Name:"web_search", Description:"互联网搜索", Parameters:map[string]any{"query": map[string]string{"type":"string"}}}}
|
||||
return []plugin.ToolDefinition{{
|
||||
Name: "web_search",
|
||||
Description: "Search the internet via SearXNG. Returns top results with title, URL and snippet.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"query": map[string]string{"type": "string", "description": "Search query"},
|
||||
},
|
||||
"required": []string{"query"},
|
||||
},
|
||||
}}
|
||||
}
|
||||
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
|
||||
return "[搜索] 引擎未配置", nil
|
||||
|
||||
type searxResult struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (p *Plugin) Execute(ctx context.Context, _ string, params map[string]any) (string, error) {
|
||||
query, _ := params["query"].(string)
|
||||
if query == "" {
|
||||
return "", fmt.Errorf("missing query")
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/search?q=%s&format=json", p.searxngURL, url.QueryEscape(query))
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("search request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("search API %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Results []searxResult `json:"results"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", fmt.Errorf("parse results: %w", err)
|
||||
}
|
||||
if len(result.Results) == 0 {
|
||||
return fmt.Sprintf("搜索 %q 没有结果", query), nil
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "搜索 %q 的结果:\n", query)
|
||||
for i, r := range result.Results {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(&sb, "%d. %s\n %s\n %s\n", i+1, r.Title, r.URL, r.Content)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
Reference in new issue
Block a user