From f38d4e7ad5f758f2d0727947203e3f6e37461983 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 14 Aug 2026 17:21:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=86=85=E7=BD=AE=E5=B7=A5=E5=85=B7=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=88=B0=20internal/tools/builtin=20=E5=AD=90?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E6=B5=8B=E8=AF=95=E7=A7=BB=E8=87=B3=E7=88=B6?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/bot/bot.go | 3 +- .../tools/{ => builtin}/calculator_tool.go | 2 +- internal/tools/{ => builtin}/random_tool.go | 2 +- internal/tools/{ => builtin}/time_tool.go | 2 +- internal/tools/builtin_test.go | 73 +++++++++++++ internal/tools/tools_test.go | 100 ++++++------------ 6 files changed, 111 insertions(+), 71 deletions(-) rename internal/tools/{ => builtin}/calculator_tool.go (98%) rename internal/tools/{ => builtin}/random_tool.go (98%) rename internal/tools/{ => builtin}/time_tool.go (98%) create mode 100644 internal/tools/builtin_test.go diff --git a/internal/bot/bot.go b/internal/bot/bot.go index e31006a..bb8d4f3 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -14,6 +14,7 @@ import ( "github.com/tidwall/gjson" "myaibot/internal/config" "myaibot/internal/tools" + "myaibot/internal/tools/builtin" ) const ( @@ -21,7 +22,7 @@ const ( maxToolRounds = 5 ) -var toolRegistry = tools.NewRegistry(tools.TimeTool{}, tools.CalculatorTool{}, tools.RandomTool{}) +var toolRegistry = tools.NewRegistry(builtin.TimeTool{}, builtin.CalculatorTool{}, builtin.RandomTool{}) type Bot struct { clients map[string]*openai.Client diff --git a/internal/tools/calculator_tool.go b/internal/tools/builtin/calculator_tool.go similarity index 98% rename from internal/tools/calculator_tool.go rename to internal/tools/builtin/calculator_tool.go index ac48603..9c8e668 100644 --- a/internal/tools/calculator_tool.go +++ b/internal/tools/builtin/calculator_tool.go @@ -1,4 +1,4 @@ -package tools +package builtin import ( "encoding/json" diff --git a/internal/tools/random_tool.go b/internal/tools/builtin/random_tool.go similarity index 98% rename from internal/tools/random_tool.go rename to internal/tools/builtin/random_tool.go index 7c74cc3..4294ed6 100644 --- a/internal/tools/random_tool.go +++ b/internal/tools/builtin/random_tool.go @@ -1,4 +1,4 @@ -package tools +package builtin import ( "encoding/json" diff --git a/internal/tools/time_tool.go b/internal/tools/builtin/time_tool.go similarity index 98% rename from internal/tools/time_tool.go rename to internal/tools/builtin/time_tool.go index 3c259c9..44177af 100644 --- a/internal/tools/time_tool.go +++ b/internal/tools/builtin/time_tool.go @@ -1,4 +1,4 @@ -package tools +package builtin import ( "encoding/json" diff --git a/internal/tools/builtin_test.go b/internal/tools/builtin_test.go new file mode 100644 index 0000000..5e62f76 --- /dev/null +++ b/internal/tools/builtin_test.go @@ -0,0 +1,73 @@ +package tools_test + +import ( + "encoding/json" + "strings" + "testing" + + "myaibot/internal/tools/builtin" +) + +func TestCalculator(t *testing.T) { + cases := []struct { + name string + expr string + want string + }{ + {"四则运算", "(12 + 3) * 4", "60"}, + {"幂运算", "2^10", "1024"}, + {"浮点", "7 / 2", "3.5"}, + {"小数精度", "1 / 3", "0.33333333"}, + {"布尔", "2 > 1", "true"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + args, _ := json.Marshal(map[string]string{"expression": c.expr}) + got, err := builtin.CalculatorTool{}.Execute(args) + if err != nil { + t.Fatalf("Execute 出错: %v", err) + } + if got != c.want { + t.Errorf("Execute(%q) = %q, want %q", c.expr, got, c.want) + } + }) + } +} + +func TestCalculatorInvalid(t *testing.T) { + args, _ := json.Marshal(map[string]string{"expression": "1 +"}) + if _, err := (builtin.CalculatorTool{}).Execute(args); err == nil { + t.Error("非法表达式应返回错误") + } +} + +func TestRandom(t *testing.T) { + args, _ := json.Marshal(map[string]any{"min": 1, "max": 10}) + for i := 0; i < 100; i++ { + out, err := builtin.RandomTool{}.Execute(args) + if err != nil { + t.Fatalf("Execute 出错: %v", err) + } + var v int + if err := json.Unmarshal([]byte(out), &v); err != nil { + t.Fatalf("结果 %q 解析失败: %v", out, err) + } + if v < 1 || v > 10 { + t.Fatalf("结果 %q 不在 [1,10] 内", out) + } + } + bad, _ := json.Marshal(map[string]any{"min": 10, "max": 1}) + if _, err := (builtin.RandomTool{}).Execute(bad); err == nil { + t.Error("min>max 应返回错误") + } +} + +func TestTimeTool(t *testing.T) { + out, err := builtin.TimeTool{}.Execute(nil) + if err != nil { + t.Fatalf("Execute 出错: %v", err) + } + if !strings.Contains(out, "20") { + t.Errorf("时间输出异常: %q", out) + } +} diff --git a/internal/tools/tools_test.go b/internal/tools/tools_test.go index 63b2fb2..56ec24c 100644 --- a/internal/tools/tools_test.go +++ b/internal/tools/tools_test.go @@ -2,93 +2,59 @@ package tools import ( "encoding/json" + "fmt" "strings" "testing" ) -func TestCalculator(t *testing.T) { - cases := []struct { - name string - expr string - want string - }{ - {"四则运算", "(12 + 3) * 4", "60"}, - {"幂运算", "2^10", "1024"}, - {"浮点", "7 / 2", "3.5"}, - {"小数精度", "1 / 3", "0.33333333"}, - {"布尔", "2 > 1", "true"}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - args, _ := json.Marshal(map[string]string{"expression": c.expr}) - got, err := CalculatorTool{}.Execute(args) - if err != nil { - t.Fatalf("Execute 出错: %v", err) - } - if got != c.want { - t.Errorf("Execute(%q) = %q, want %q", c.expr, got, c.want) - } - }) - } +type stubTool struct{} + +func (stubTool) Name() string { return "stub" } +func (stubTool) Description() string { return "测试桩工具" } +func (stubTool) Parameters() map[string]any { + return map[string]any{"type": "object"} +} +func (stubTool) Execute(args json.RawMessage) (string, error) { + return fmt.Sprintf("stub:%s", args), nil } -func TestCalculatorInvalid(t *testing.T) { - args, _ := json.Marshal(map[string]string{"expression": "1 +"}) - if _, err := (CalculatorTool{}).Execute(args); err == nil { - t.Error("非法表达式应返回错误") - } -} +type stubTool2 struct{} -func TestRandom(t *testing.T) { - args, _ := json.Marshal(map[string]any{"min": 1, "max": 10}) - for i := 0; i < 100; i++ { - out, err := RandomTool{}.Execute(args) - if err != nil { - t.Fatalf("Execute 出错: %v", err) - } - var v int - if err := json.Unmarshal([]byte(out), &v); err != nil { - t.Fatalf("结果 %q 解析失败: %v", out, err) - } - if v < 1 || v > 10 { - t.Fatalf("结果 %q 不在 [1,10] 内", out) - } - } - bad, _ := json.Marshal(map[string]any{"min": 10, "max": 1}) - if _, err := (RandomTool{}).Execute(bad); err == nil { - t.Error("min>max 应返回错误") - } +func (stubTool2) Name() string { return "stub2" } +func (stubTool2) Description() string { return "测试桩工具2" } +func (stubTool2) Parameters() map[string]any { + return map[string]any{"type": "object"} } - -func TestTimeTool(t *testing.T) { - out, err := TimeTool{}.Execute(nil) - if err != nil { - t.Fatalf("Execute 出错: %v", err) - } - if !strings.Contains(out, "20") { - t.Errorf("时间输出异常: %q", out) - } +func (stubTool2) Execute(args json.RawMessage) (string, error) { + return "stub2", nil } func TestRegistry(t *testing.T) { - r := NewRegistry(TimeTool{}, CalculatorTool{}, RandomTool{}) - if _, ok := r.Get("get_current_time"); !ok { - t.Error("get_current_time 未注册") + r := NewRegistry(stubTool{}, stubTool2{}) + if _, ok := r.Get("stub"); !ok { + t.Error("stub 未注册") } if _, ok := r.Get("nonexistent"); ok { t.Error("未知工具不应存在") } - if len(r.ParamList()) != 3 { - t.Errorf("ParamList 数量 = %d, want 3", len(r.ParamList())) + if len(r.ParamList()) != 2 { + t.Errorf("ParamList 数量 = %d, want 2", len(r.ParamList())) } if _, err := r.Execute("nonexistent", nil); err == nil { t.Error("执行未知工具应返回错误") } - list := r.List() - if len(list) != 3 { - t.Fatalf("List 数量 = %d, want 3", len(list)) + out, err := r.Execute("stub", json.RawMessage(`{"a":1}`)) + if err != nil { + t.Fatalf("Execute 出错: %v", err) } - wantOrder := []string{"calculate", "get_current_time", "random_number"} + if out != `stub:{"a":1}` { + t.Errorf("Execute 输出 = %q", out) + } + list := r.List() + if len(list) != 2 { + t.Fatalf("List 数量 = %d, want 2", len(list)) + } + wantOrder := []string{"stub", "stub2"} for i, want := range wantOrder { if !strings.HasPrefix(list[i], want+" - ") { t.Errorf("List[%d] = %q, want 前缀 %q", i, list[i], want+" - ")