aster的工具系统为Agent提供了与外部世界交互的能力。从文件操作到网络请求,工具让Agent能够执行实际任务。
工具(Tool)是Agent可以调用的函数,用于执行特定任务:
Agent思考 → 决定使用工具 → 调用工具 → 获取结果 → 继续思考
type Tool interface {
// 工具名称(唯一标识)
Name() string
// 工具描述(告诉LLM这个工具的用途)
Description() string
// 输入参数schema(JSON Schema格式)
InputSchema() map[string]interface{}
// 执行工具
Execute(ctx context.Context, input map[string]interface{}) (interface{}, error)
}
type CalculatorTool struct{}
func (t *CalculatorTool) Name() string {
return "calculator"
}
func (t *CalculatorTool) Description() string {
return "执行数学计算,支持加减乘除运算"
}
func (t *CalculatorTool) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"expression": map[string]interface{}{
"type": "string",
"description": "数学表达式,例如: 2+2, 10*5",
},
},
"required": []string{"expression"},
}
}
func (t *CalculatorTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
expr := input["expression"].(string)
// 执行计算(实际应使用安全的表达式解析器)
result, err := evaluate(expr)
if err != nil {
return map[string]interface{}{
"ok": false,
"error": err.Error(),
}, nil // 注意:返回nil error
}
return map[string]interface{}{
"ok": true,
"result": result,
}, nil
}
aster提供丰富的内置工具,覆盖常见使用场景。
读取文件内容(支持分页):
// 工具调用
{
"name": "Read",
"input": {
"path": "/README.md",
"offset": 0, // 起始行号(可选)
"limit": 100 // 读取行数(可选)
}
}
// 返回结果
{
"ok": true,
"content": "文件内容...",
"lines": 100,
"total_lines": 250,
"has_more": true
}
写入文件:
{
"name": "Write",
"input": {
"path": "/output.txt",
"content": "Hello World"
}
}
精确编辑(字符串替换):
{
"name": "Edit",
"input": {
"path": "/config.json",
"old_string": "\"debug\": false",
"new_string": "\"debug\": true",
"replace_all": false
}
}
列出目录:
{
"name": "Ls",
"input": {
"path": "/src"
}
}
// 返回
{
"ok": true,
"entries": [
{"name": "main.go", "size": 1024, "is_dir": false},
{"name": "utils", "size": 0, "is_dir": true}
]
}
Glob模式匹配:
{
"name": "Glob",
"input": {
"pattern": "**/*.go",
"path": "/src"
}
}
正则搜索:
{
"name": "Grep",
"input": {
"pattern": "func.*Error",
"path": "/src",
"glob": "*.go"
}
}
// 返回
{
"ok": true,
"matches": [
{
"file": "/src/main.go",
"line": 42,
"content": "func handleError(err error) {"
}
]
}
执行Bash命令:
{
"name": "Bash",
"input": {
"command": "go test ./...",
"timeout": 30000 // 毫秒
}
}
// 返回
{
"ok": true,
"stdout": "PASS\nok\t...",
"stderr": "",
"exit_code": 0
}
HTTP请求:
{
"name": "http_fetch",
"input": {
"url": "https://api.example.com/data",
"method": "GET",
"headers": {"Authorization": "Bearer token"},
"body": null
}
}
网络搜索(Tavily API):
{
"name": "WebSearch",
"input": {
"query": "Go语言性能优化",
"search_type": "general", // general/news/finance
"max_results": 5
}
}
列出待办事项:
{
"name": "todo_list",
"input": {}
}
添加待办:
{
"name": "todo_add",
"input": {
"title": "实现用户认证",
"description": "添加JWT认证",
"priority": "high"
}
}
更新待办状态:
{
"name": "todo_update",
"input": {
"id": "todo-123",
"status": "completed"
}
}
import (
"github.com/astercloud/aster/pkg/tools"
"github.com/astercloud/aster/pkg/tools/builtin"
)
// 创建工具注册表
registry := tools.NewRegistry()
// 方式1:注册所有内置工具
builtin.RegisterAll(registry)
// 方式2:选择性注册
registry.Register(builtin.NewFileSystemTool())
registry.Register(builtin.NewBashTool())
registry.Register(builtin.NewHTTPTool())
// 注册自定义工具
registry.Register(&CalculatorTool{})
registry.Register(&WeatherTool{})
ag, err := agent.Create(ctx, &types.AgentConfig{
TemplateID: "assistant",
// 指定允许使用的工具
Tools: []interface{}{
"Read", // 工具名称
"Write",
"Bash",
&MyTool{}, // 或直接传入工具实例
},
}, &agent.Dependencies{
ToolRegistry: registry,
// ...其他依赖
})
package mytools
import (
"context"
"encoding/json"
"net/http"
)
type WeatherTool struct{}
func (t *WeatherTool) Name() string {
return "get_weather"
}
func (t *WeatherTool) Description() string {
return "获取指定城市的天气信息"
}
func (t *WeatherTool) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]interface{}{
"type": "string",
"description": "城市名称,例如: Beijing, Shanghai",
},
},
"required": []string{"city"},
}
}
func (t *WeatherTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
city := input["city"].(string)
// 调用天气API
weather, err := t.fetchWeather(ctx, city)
if err != nil {
return map[string]interface{}{
"ok": false,
"error": err.Error(),
}, nil
}
return map[string]interface{}{
"ok": true,
"city": city,
"temperature": weather.Temp,
"condition": weather.Condition,
"humidity": weather.Humidity,
}, nil
}
func (t *WeatherTool) fetchWeather(ctx context.Context, city string) (*Weather, error) {
// 实际API调用逻辑
url := fmt.Sprintf("https://api.weather.com/v1/current?city=%s", city)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var weather Weather
json.NewDecoder(resp.Body).Decode(&weather)
return &weather, nil
}
type CounterTool struct {
count int
mu sync.Mutex
}
func (t *CounterTool) Name() string {
return "counter"
}
func (t *CounterTool) Description() string {
return "计数器工具,可以增加或查询计数"
}
func (t *CounterTool) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"action": map[string]interface{}{
"type": "string",
"enum": []string{"increment", "get", "reset"},
},
},
"required": []string{"action"},
}
}
func (t *CounterTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
action := input["action"].(string)
t.mu.Lock()
defer t.mu.Unlock()
switch action {
case "increment":
t.count++
return map[string]interface{}{
"ok": true,
"count": t.count,
}, nil
case "get":
return map[string]interface{}{
"ok": true,
"count": t.count,
}, nil
case "reset":
t.count = 0
return map[string]interface{}{
"ok": true,
"message": "计数器已重置",
}, nil
default:
return map[string]interface{}{
"ok": false,
"error": "未知操作",
}, nil
}
}
type AsyncTool struct{}
func (t *AsyncTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
// 创建后台任务
taskID := uuid.New().String()
go func() {
// 长时间运行的任务
time.Sleep(10 * time.Second)
result := processLongTask(input)
// 将结果存储到某处
store.SaveResult(taskID, result)
}()
return map[string]interface{}{
"ok": true,
"task_id": taskID,
"status": "processing",
"message": "任务已启动,请使用task_id查询结果",
}, nil
}
1. LLM决定使用工具
│
▼
2. 生成tool_use块
│ {
│ "type": "tool_use",
│ "id": "toolu_123",
│ "name": "Read",
│ "input": {"path": "/file.txt"}
│ }
▼
3. Agent提取工具调用
│
▼
4. 查找工具(Registry.GetTool)
│
▼
5. 验证输入(InputSchema)
│
▼
6. 通过中间件栈(WrapToolCall)
│
▼
7. 执行工具(tool.Execute)
│
▼
8. 构造tool_result消息
│ {
│ "type": "tool_result",
│ "tool_use_id": "toolu_123",
│ "content": "{\"ok\": true, ...}"
│ }
▼
9. 继续对话
重要:工具应该返回结构化错误,而不是Go error:
// ✅ 推荐:返回结构化错误
func (t *MyTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
result, err := doSomething()
if err != nil {
return map[string]interface{}{
"ok": false,
"error": err.Error(),
"suggestions": []string{
"检查输入参数",
"确认权限设置",
},
}, nil // 返回nil error!
}
return map[string]interface{}{
"ok": true,
"result": result,
}, nil
}
// ❌ 不推荐:直接返回error
func (t *MyTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
result, err := doSomething()
if err != nil {
return nil, err // LLM看不到错误信息
}
return result, nil
}
原因:LLM需要看到错误信息才能尝试恢复或给出建议。
type CompositeTool struct {
tools []Tool
}
func (t *CompositeTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
results := make([]interface{}, 0)
// 依次执行多个工具
for _, tool := range t.tools {
result, err := tool.Execute(ctx, input)
if err != nil {
return nil, err
}
results = append(results, result)
}
return map[string]interface{}{
"ok": true,
"results": results,
}, nil
}
type ProxyTool struct {
target Tool
}
func (t *ProxyTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
// 前置处理
log.Printf("执行工具: %s", t.target.Name())
start := time.Now()
// 执行目标工具
result, err := t.target.Execute(ctx, input)
// 后置处理
duration := time.Since(start)
log.Printf("工具完成: %s, 耗时: %v", t.target.Name(), duration)
return result, err
}
type ConditionalTool struct {
condition func(map[string]interface{}) bool
trueTool Tool
falseTool Tool
}
func (t *ConditionalTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
if t.condition(input) {
return t.trueTool.Execute(ctx, input)
}
return t.falseTool.Execute(ctx, input)
}
// ✅ 推荐:具体的描述
func (t *Tool) Description() string {
return "从指定URL下载文件到本地目录。支持HTTP/HTTPS,可配置超时。返回下载的文件路径和大小。"
}
// ❌ 不推荐:模糊的描述
func (t *Tool) Description() string {
return "下载文件"
}
// ✅ 推荐:详细的schema
func (t *Tool) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]interface{}{
"type": "string",
"description": "要下载的文件URL",
"pattern": "^https?://",
},
"output_path": map[string]interface{}{
"type": "string",
"description": "保存文件的本地路径",
},
"timeout": map[string]interface{}{
"type": "integer",
"description": "超时时间(秒),默认60秒",
"default": 60,
"minimum": 1,
"maximum": 300,
},
},
"required": []string{"url"},
}
}
// ✅ 推荐:结构化返回
return map[string]interface{}{
"ok": true,
"file_path": "/downloads/file.pdf",
"file_size": 1048576,
"mime_type": "application/pdf",
"duration": "2.5s",
}, nil
// ❌ 不推荐:字符串返回
return "文件已下载到 /downloads/file.pdf", nil
// 工具应该尽可能幂等
func (t *CreateFileTool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
path := input["path"].(string)
// 检查文件是否已存在
if fileExists(path) {
return map[string]interface{}{
"ok": true,
"existed": true,
"message": "文件已存在",
}, nil
}
// 创建文件
return createFile(path)
}
func (t *Tool) Execute(ctx context.Context, input map[string]interface{}) (interface{}, error) {
// 使用context控制超时
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// 可取消的操作
result := make(chan interface{}, 1)
go func() {
result <- doWork()
}()
select {
case r := <-result:
return r, nil
case <-ctx.Done():
return map[string]interface{}{
"ok": false,
"error": "操作超时",
}, nil
}
}