【Eino 框架入门】给 Agent 装上"手"——工具调用
前几篇 Agent 只能聊天,这篇让它能操作文件系统:读文件、搜代码、列目录。
ch03 vs ch04
| ch03 | ch04 | |
|---|---|---|
| Agent | ChatModelAgent | deep Agent |
| 能力 | 只能聊天 | 能调用工具 |
| 后端 | 无 | LocalBackend |
打个比方:ch03 的 Agent 像个只能说话的顾问,ch04 的 Agent 像个能动手的工程师。
核心代码
// 1. 创建本地后端(提供文件系统工具)
backend, _ := localbk.NewBackend(ctx, &localbk.Config{})
// 2. 创建 deep Agent(支持工具调用)
agent, _ := deep.New(ctx, &deep.Config{
ChatModel: cm,
Instruction: "You are a helpful assistant...",
Backend: backend, // 注入后端,提供工具
StreamingShell: backend, // 流式执行工具
MaxIteration: 50, // 最多迭代 50 轮
})
// 3. 运行 Agent(和之前一样)
events := runner.Run(ctx, history)
三行关键代码:
NewBackend- 创建工具提供者deep.New- 创建支持工具的 AgentBackend: backend- 把工具给 Agent
工具调用流程
用户: "帮我看看 main.go 里有什么"
↓
Agent 思考: 需要调用 read_file 工具
↓
[tool call] read_file({"file_path": "/path/main.go"})
↓
LocalBackend 执行工具,返回文件内容
↓
[tool result] {"content": "package main\n\nfunc main() {...}"}
↓
Agent 基于结果回复: "main.go 是程序入口,定义了 main 函数..."
Agent 会自动判断:这个问题需要工具吗?需要就调用,不需要就直接回答。
LocalBackend 提供的工具
| 工具 | 功能 | 示例 |
|---|---|---|
| ls | 列目录 | ls({"path": "/tmp"}) |
| read_file | 读文件 | read_file({"file_path": "/tmp/a.txt"}) |
| glob | 模式匹配 | glob({"pattern": "**/*.go"}) |
| grep | 搜索内容 | grep({"pattern": "func main", "path": "."}) |
注意:grep 依赖 rg (ripgrep),需要先安装:
brew install ripgrep
deep Agent 是什么
deep 是 Eino 预构建的 Agent,特点:
- 自动工具选择:根据用户问题自动决定调用哪个工具
- 多轮迭代:调用工具后继续思考,可能再调用其他工具
- 流式输出:支持流式返回结果
源码位置:github.com/cloudwego/eino/adk/prebuilt/deep
type Config struct {
ChatModel model.BaseChatModel // 大模型
Instruction string // 系统提示词
Backend filesystem.Backend // 工具后端
StreamingShell filesystem.StreamingShell // 流式执行
MaxIteration int // 最大迭代轮数
}
MaxIteration 控制工具调用循环的最大次数。比如用户问"找出所有 TODO 并整理",Agent 可能需要:
- 调用
glob找到所有.go文件 - 对每个文件调用
grep搜索 TODO - 整理结果回复用户
这就是多轮迭代。
事件流的变化
ch04 的事件流比 ch03 多了两种消息:
Tool Call(模型决定调用工具):
// Message.ToolCalls 包含要调用的工具
for _, tc := range mv.Message.ToolCalls {
fmt.Printf("[tool call] %s(%s)\n", tc.Function.Name, tc.Function.Arguments)
}
Tool Result(工具执行结果):
// Role == schema.Tool 表示工具返回
if mv.Role == schema.Tool {
content := drainToolResult(mv)
fmt.Printf("[tool result] %s\n", content)
}
完整的消息序列:
[Assistant] 我来帮你看看...
[tool call] read_file({"file_path": "/path/main.go"})
[tool result] {"content": "package main..."}
[Assistant] main.go 的内容是...
后端是什么
Backend 是工具的提供者。LocalBackend 提供本地文件系统访问能力。
type Backend interface {
// 返回可用的工具列表
Tools(ctx context.Context) ([]*schema.ToolInfo, error)
// 执行工具
Invoke(ctx context.Context, toolName string, args string) (string, error)
}
除了 LocalBackend,还可以实现其他 Backend:
- HTTPBackend:提供 HTTP 请求工具
- DBBackend:提供数据库查询工具
- CustomBackend:自定义业务工具
用法
cd /Users/yuanjian/go/src/cloudwego/eino-demo
# 新建会话
go run ./cmd/ch04
# 试着问:
# you> 列出当前目录的文件
# you> 读取 go.mod 文件
# you> 搜索所有包含 "func main" 的文件
这篇的局限
LocalBackend 只能访问本地文件。如果要:
- 调用外部 API(天气、搜索)
- 查询数据库
- 执行远程命令
需要自己实现 Backend 或使用其他预构建的后端。