refactor: 项目结构重组,src/ 扁平化为根目录,提取 pkg/ 子包
- 模块名重命名 yunshu -> hub.gaomia.site/titor/YunShu - Go 版本升级 1.21 -> 1.25 - src/ 目录删除,所有文件移至根目录 - 新增 pkg/mdprint/: Markdown AST 解析+ANSI 渲染 - 新增 pkg/style/: 终端颜色样式(8色 ANSI + 24位真彩色) - 新增 pkg/termui/: 终端输入组件(交互式输入/密码/确认) - 更新文档:AGENTS.md、architecture.md、changelog.md、taolun.md - gitignore 通配符修复 yunshu.exe -> yunshu.exe*
This commit is contained in:
224
tool.go
Normal file
224
tool.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var registeredTools = make(map[string]*ToolDef)
|
||||
|
||||
func RegisterTool(td *ToolDef) {
|
||||
registeredTools[td.Name] = td
|
||||
}
|
||||
|
||||
func ListRegisteredTools() []*ToolDef {
|
||||
list := make([]*ToolDef, 0, len(registeredTools))
|
||||
for _, td := range registeredTools {
|
||||
list = append(list, td)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func GetToolDefs(names []string) []ToolDef {
|
||||
defs := make([]ToolDef, 0, len(names))
|
||||
for _, name := range names {
|
||||
if td, ok := registeredTools[name]; ok {
|
||||
defs = append(defs, *td)
|
||||
}
|
||||
}
|
||||
return defs
|
||||
}
|
||||
|
||||
func ExecuteTool(tc ToolCall) (string, error) {
|
||||
td, ok := registeredTools[tc.Function.Name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("未知工具: %s", tc.Function.Name)
|
||||
}
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil {
|
||||
return "", fmt.Errorf("解析工具参数失败: %w", err)
|
||||
}
|
||||
|
||||
return td.Execute(args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTool(&ToolDef{
|
||||
Name: "http-get",
|
||||
Description: "发送 HTTP GET 请求获取数据",
|
||||
Parameters: ToolParameter{
|
||||
Type: "object",
|
||||
Properties: map[string]ToolProperty{
|
||||
"url": {Type: "string", Description: "请求的完整 URL 地址"},
|
||||
"headers": {Type: "string", Description: "请求头,JSON 格式的键值对字符串,如 {\"User-Agent\": \"...\"}"},
|
||||
},
|
||||
Required: []string{"url"},
|
||||
},
|
||||
Execute: func(args map[string]interface{}) (string, error) {
|
||||
url, _ := args["url"].(string)
|
||||
if url == "" {
|
||||
return "", fmt.Errorf("缺少 url 参数")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
|
||||
if headersStr, ok := args["headers"].(string); ok && headersStr != "" {
|
||||
var headers map[string]string
|
||||
if err := json.Unmarshal([]byte(headersStr), &headers); err == nil {
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body)), nil
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
},
|
||||
})
|
||||
|
||||
RegisterTool(&ToolDef{
|
||||
Name: "skill",
|
||||
Description: "加载指定名称的 Skill 知识内容到当前上下文,获取专业知识",
|
||||
Parameters: ToolParameter{
|
||||
Type: "object",
|
||||
Properties: map[string]ToolProperty{
|
||||
"name": {Type: "string", Description: "Skill 名称,如 msn-weather-api"},
|
||||
},
|
||||
Required: []string{"name"},
|
||||
},
|
||||
Execute: func(args map[string]interface{}) (string, error) {
|
||||
name, _ := args["name"].(string)
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("缺少 name 参数")
|
||||
}
|
||||
return LoadSkill(name)
|
||||
},
|
||||
})
|
||||
|
||||
RegisterTool(&ToolDef{
|
||||
Name: "read-file",
|
||||
Description: "读取本地文件内容",
|
||||
Parameters: ToolParameter{
|
||||
Type: "object",
|
||||
Properties: map[string]ToolProperty{
|
||||
"path": {Type: "string", Description: "文件路径,相对于项目根目录"},
|
||||
},
|
||||
Required: []string{"path"},
|
||||
},
|
||||
Execute: func(args map[string]interface{}) (string, error) {
|
||||
path, _ := args["path"].(string)
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("缺少 path 参数")
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取文件失败: %w", err)
|
||||
}
|
||||
return strings.TrimSpace(string(data)), nil
|
||||
},
|
||||
})
|
||||
|
||||
RegisterTool(&ToolDef{
|
||||
Name: "geocode",
|
||||
Description: "查询城市或地点的经纬度坐标,返回 lat/lon/name/country。支持中文城市名(如 北京、上海、成都)和英文名",
|
||||
Parameters: ToolParameter{
|
||||
Type: "object",
|
||||
Properties: map[string]ToolProperty{
|
||||
"city": {Type: "string", Description: "城市名称,支持中文(如 北京)或英文(如 Beijing)"},
|
||||
},
|
||||
Required: []string{"city"},
|
||||
},
|
||||
Execute: func(args map[string]interface{}) (string, error) {
|
||||
city, _ := args["city"].(string)
|
||||
if city == "" {
|
||||
return "", fmt.Errorf("缺少 city 参数")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://wttr.in/%s?format=j1", city)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求 wttr.in 失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("wttr.in 返回 HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result struct {
|
||||
NearestArea []struct {
|
||||
AreaName []struct {
|
||||
Value string `json:"value"`
|
||||
} `json:"areaName"`
|
||||
Country []struct {
|
||||
Value string `json:"value"`
|
||||
} `json:"country"`
|
||||
Latitude string `json:"latitude"`
|
||||
Longitude string `json:"longitude"`
|
||||
Population string `json:"population"`
|
||||
} `json:"nearest_area"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return "", fmt.Errorf("解析 wttr.in 响应失败: %w", err)
|
||||
}
|
||||
|
||||
if len(result.NearestArea) == 0 {
|
||||
return "", fmt.Errorf("未找到城市: %s", city)
|
||||
}
|
||||
|
||||
area := result.NearestArea[0]
|
||||
name := ""
|
||||
if len(area.AreaName) > 0 {
|
||||
name = area.AreaName[0].Value
|
||||
}
|
||||
country := ""
|
||||
if len(area.Country) > 0 {
|
||||
country = area.Country[0].Value
|
||||
}
|
||||
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"lat": area.Latitude,
|
||||
"lon": area.Longitude,
|
||||
"name": name,
|
||||
"country": country,
|
||||
})
|
||||
return string(out), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user