- Registry 从纯接口升级为完整实现:Register/Execute/路由查找/错误自愈 - 新增 BaseTool 接口,统一工具契约 - 新增 ReadFileTool,首个真实工具实现(路径防穿越 + 截断保护) - 新增 String() 方法,Registry/Engine 调试可视化 - 新增 ToolCall 执行状态日志 - cmd/claw 切换为真实 Registry + ReadFileTool
105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"go-tiny-claw/internal/schema"
|
|
)
|
|
|
|
// BaseTool 是所有具体工具必须实现的通用接口
|
|
type BaseTool interface {
|
|
// Name 返回工具的全局唯一名称 (大模型通过这个名字调用它)
|
|
Name() string
|
|
|
|
// Definition 返回用于提交给大模型的工具元信息和参数 JSON Schema
|
|
Definition() schema.ToolDefinition
|
|
|
|
// Execute 接收大模型吐出的 JSON 参数,执行具体业务逻辑
|
|
// 注意:参数是 json.RawMessage,反序列化由各个具体工具内部自行处理
|
|
Execute(ctx context.Context, args json.RawMessage) (string, error)
|
|
}
|
|
|
|
// Registry 定义了工具的注册与分发接口
|
|
type Registry interface {
|
|
// Register 挂载一个新的工具到系统中
|
|
Register(tool BaseTool)
|
|
|
|
// GetAvailableTools 返回当前系统挂载的所有工具的 Schema,供 Main Loop 交给 Provider
|
|
GetAvailableTools() []schema.ToolDefinition
|
|
|
|
// Execute 实际路由并执行模型请求的工具调用
|
|
Execute(ctx context.Context, call schema.ToolCall) schema.ToolResult
|
|
}
|
|
|
|
// registryImpl 是 Registry 接口的默认实现
|
|
type registryImpl struct {
|
|
// 使用 map 以工具的 Name 作为 Key 进行快速 O(1) 路由查找
|
|
tools map[string]BaseTool
|
|
}
|
|
|
|
func NewRegistry() Registry {
|
|
return ®istryImpl{
|
|
tools: make(map[string]BaseTool),
|
|
}
|
|
}
|
|
|
|
func (r *registryImpl) String() string {
|
|
var names []string
|
|
for name := range r.tools {
|
|
names = append(names, name)
|
|
}
|
|
return fmt.Sprintf("Registry{tools: %v}", names)
|
|
}
|
|
|
|
func (r *registryImpl) Register(tool BaseTool) {
|
|
name := tool.Name()
|
|
if _, exists := r.tools[name]; exists {
|
|
log.Printf("[Warning] 工具 '%s' 已经被注册,将被覆盖。\n", name)
|
|
}
|
|
r.tools[name] = tool
|
|
log.Printf("[Registry] 成功挂载工具: %s\n", name)
|
|
}
|
|
|
|
func (r *registryImpl) GetAvailableTools() []schema.ToolDefinition {
|
|
var defs []schema.ToolDefinition
|
|
for _, tool := range r.tools {
|
|
defs = append(defs, tool.Definition())
|
|
}
|
|
return defs
|
|
}
|
|
|
|
func (r *registryImpl) Execute(ctx context.Context, call schema.ToolCall) schema.ToolResult {
|
|
// 1. 路由查找:如果在注册表中找不到该工具,这是模型产生了幻觉,直接向模型抛出错误
|
|
tool, exists := r.tools[call.Name]
|
|
if !exists {
|
|
errMsg := fmt.Sprintf("Error: 系统中不存在名为 '%s' 的工具。", call.Name)
|
|
return schema.ToolResult{
|
|
ToolCallID: call.ID,
|
|
Output: errMsg,
|
|
IsError: true, // 标记为错误,模型看到后会尝试纠正
|
|
}
|
|
}
|
|
|
|
// 2. 执行工具逻辑:将原始的 JSON 字节流直接丢给具体工具
|
|
output, err := tool.Execute(ctx, call.Arguments)
|
|
|
|
// 3. 封装结果:将执行结果或底层物理错误封装后返回给 Main Loop
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("Error executing %s: %v", call.Name, err)
|
|
return schema.ToolResult{
|
|
ToolCallID: call.ID,
|
|
Output: errMsg,
|
|
IsError: true,
|
|
}
|
|
}
|
|
|
|
return schema.ToolResult{
|
|
ToolCallID: call.ID,
|
|
Output: output,
|
|
IsError: false,
|
|
}
|
|
}
|