第四章:Tool 与文件系统访问
本章目标:为 Agent 添加 Tool 能力,让 Agent 能够访问文件系统。
为什么需要 Tool
前三章我们实现的 Agent 只能对话,无法执行实际操作。
Agent 的局限:
- 只能生成文本回复
- 无法访问外部资源(文件、API、数据库等)
- 无法执行实际任务(计算、查询、修改等)
Tool 的定位:
- Tool 是 Agent 的能力扩展:让 Agent 能够执行具体操作
- Tool 封装了具体实现:Agent 不关心 Tool 内部如何工作,只关心输入输出
- Tool 可组合:一个 Agent 可以有多个 Tool,根据需要选择调用
简单类比:
- Agent = “智能助手”(能理解指令,但需要工具才能执行)
- Tool = “工具箱”(文件操作、网络请求、数据库查询等)
为什么需要文件系统能力
本示例是 ChatWithDoc(与文档对话),目标是帮助用户学习 Eino 框架并编写 Eino 代码。那么,最好的文档是什么?
答案就是:Eino 仓库的代码本身。
- Code: 源代码展示了框架的真实实现
- Comment: 代码注释提供了设计思路和使用说明
- Examples: 示例代码演示了最佳实践
通过文件系统访问能力,Agent 可以直接读取 Eino 源码、注释和示例,为用户提供最准确、最及时的技术支持。
关键概念
Tool 接口
Tool 是 Eino 中定义可执行能力的接口:
// BaseTool 提供工具的元信息,ChatModel 使用这些信息决定是否以及如何调用工具
type BaseTool interface {
Info(ctx context.Context) (*schema.ToolInfo, error)
}
// InvokableTool 是可以被 ToolsNode 执行的工具
type InvokableTool interface {
BaseTool
// InvokableRun 执行工具,参数是 JSON 编码的字符串,返回字符串结果
InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
}
// StreamableTool 是 InvokableTool 的流式变体
type StreamableTool interface {
BaseTool
// StreamableRun 流式执行工具,返回 StreamReader
StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
}
接口层次:
BaseTool:基础接口,只提供元信息InvokableTool:可执行工具(继承 BaseTool)StreamableTool:流式工具(继承 BaseTool)
Backend 接口
Backend 是 Eino 中用于文件系统操作的抽象接口:
type Backend interface {
// 列出目录下的文件信息
LsInfo(ctx context.Context, req *LsInfoRequest) ([]FileInfo, error)
// 读取文件内容,支持按行偏移和限制
Read(ctx context.Context, req *ReadRequest) (*FileContent, error)
// 在文件中搜索匹配的内容
GrepRaw(ctx context.Context, req *GrepRequest) ([]GrepMatch, error)
// 根据 glob 模式匹配文件
GlobInfo(ctx context.Context, req *GlobInfoRequest) ([]FileInfo, error)
// 写入文件内容
Write(ctx context.Context, req *WriteRequest) error
// 编辑文件内容(字符串替换)
Edit(ctx context.Context, req *EditRequest) error
}
LocalBackend
LocalBackend 是 Backend 的本地文件系统实现,直接访问操作系统的文件系统:
import localbk "github.com/cloudwego/eino-ext/adk/backend/local"
backend, err := localbk.NewBackend(ctx, &localbk.Config{})
特点:
- 直接访问本地文件系统,使用 Go 标准库实现
- 支持所有 Backend 接口方法
- 支持执行 shell 命令(ExecuteStreaming)
- 路径安全:要求使用绝对路径,防止目录遍历攻击
- 零配置:开箱即用,无需额外设置
实现:使用 DeepAgent
本章使用 DeepAgent 预构建 Agent,它提供了 Backend 和 StreamingShell 的一级配置,可以方便地注册文件系统相关的工具。
从 ChatModelAgent 到 DeepAgent:何时需要切换?
前面章节一直使用 ChatModelAgent,它已经能处理多轮对话。但要访问文件系统,我们需要切换到 DeepAgent。
ChatModelAgent vs DeepAgent 对比:
| 能力 | ChatModelAgent | DeepAgent |
| 多轮对话 | ✅ | ✅ |
| 添加自定义 Tool | ✅ 手动注册每个 Tool | ✅ 手动注册或自动注册 |
| 文件系统访问(Backend) | ❌ 需手动创建并注册所有文件工具 | ✅ 一级配置,自动注册 |
| 命令执行(StreamingShell) | ❌ 需手动创建 | ✅ 一级配置,自动注册 |
| 内置任务管理 | ❌ | ✅ write_todos工具 |
| 支持子 Agent | ❌ | ✅ |
选择建议:
- 纯对话场景(无外部访问)→ 用
ChatModelAgent - 需要访问文件系统或执行命令 → 用
DeepAgent
为什么使用 DeepAgent?
相比直接使用 ChatModelAgent,DeepAgent 的优势:
- 一级配置: Backend 和 StreamingShell 是一级配置,直接传入即可
- 自动注册工具: 配置 Backend 后自动注册文件系统工具,无需手动创建
- 内置任务管理: 提供
write_todos工具,支持任务规划和跟踪 - 支持子 Agent: 可以配置专门的子 Agent 处理特定任务
- 更强大: 集成了文件系统、命令执行等多种能力
代码实现
import (
localbk "github.com/cloudwego/eino-ext/adk/backend/local"
"github.com/cloudwego/eino/adk/prebuilt/deep"
)
// 创建 LocalBackend
backend, err := localbk.NewBackend(ctx, &localbk.Config{})
// 创建 DeepAgent,自动注册文件系统工具
agent, err := deep.New(ctx, &deep.Config{
Name: "Ch04ToolAgent",
Description: "ChatWithDoc agent with filesystem access via LocalBackend.",
ChatModel: cm,
Instruction: instruction,
Backend: backend, // 提供文件系统操作能力
StreamingShell: backend, // 提供命令执行能力
MaxIteration: 50,
})
DeepAgent 自动注册的工具
当配置了 Backend 和 StreamingShell 后,DeepAgent 会自动注册以下工具:
read_file: 读取文件内容write_file: 写入文件内容edit_file: 编辑文件内容glob: 根据 glob 模式查找文件grep: 在文件中搜索内容execute: 执行 shell 命令
代码位置
- 入口代码:cmd/ch04/main.go
前置条件
与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
本章还需要设置 PROJECT_ROOT(可选,见下方运行说明)。
运行
在 examples/quickstart/chatwitheino 目录下执行:
# 可选:设置 Eino 核心库的根目录路径
# 未设置时,Agent 默认使用当前工作目录(即 chatwitheino 目录)作为根目录
# 若要让 Agent 能检索完整的 Eino 代码库,建议指向 eino 核心库根目录
export PROJECT_ROOT=/path/to/eino
# 验证路径是否正确(应该能看到 adk、components、compose 等目录)
ls $PROJECT_ROOT
go run ./cmd/ch04
PROJECT_ROOT 说明:
- 不设置时:
PROJECT_ROOT默认为当前工作目录(chatwitheino所在目录),Agent 只能访问本示例项目的文件。这对于快速试验已足够。 - 设置后:指向 Eino 核心库根目录,Agent 可以检索 Eino 框架的完整代码库(核心库、扩展库、示例库)。这是 ChatWithEino 的完整使用场景。
推荐的三仓库目录结构(如要完整体验):
eino/ # PROJECT_ROOT(Eino 核心库)
├── adk/
├── components/
├── compose/
├── ext/ # eino-ext(扩展组件,如 OpenAI、Ark 等实现)
├── examples/ # eino-examples(本仓库,本示例所在位置)
│ └── quickstart/
│ └── chatwitheino/
└── ...
可以使用 dev_setup.sh 脚本自动设置上述目录结构:
# 在 eino 根目录运行,自动克隆扩展库和示例库到正确位置
bash scripts/dev_setup.sh
输出示例:
you> 列出当前目录的文件
[assistant] 我来帮你列出当前目录的文件...
[tool call] glob(pattern: "*")
[tool result] 找到 5 个文件:
- main.go
- go.mod
- go.sum
- README.md
- cmd/
you> 读取 main.go 文件的内容
[assistant] 我来读取 main.go 文件...
[tool call] read_file(file_path: "main.go")
[tool result] 文件内容如下:
...
注意: 如果在运行过程中遇到 Tool 报错导致 Agent 中断,请不要 panic,这是正常现象。Tool 报错是常见的情况,例如参数错误、文件不存在等。如何优雅地处理 Tool 错误,我们将在下一章详细介绍。
Tool 调用流程
当 Agent 需要调用 Tool 时:
┌─────────────────────────────────────────┐
│ 用户:列出当前目录的文件 │
└─────────────────────────────────────────┘
↓
┌──────────────────────┐
│ Agent 分析意图 │
│ 决定调用 glob 工具 │
└──────────────────────┘
↓
┌──────────────────────┐
│ 生成 Tool Call │
│ {"pattern": "*"} │
└──────────────────────┘
↓
┌──────────────────────┐
│ 执行 Tool │
│ glob("*") │
└──────────────────────┘
↓
┌──────────────────────┐
│ 返回 Tool Result │
│ {"files": [...]} │
└──────────────────────┘
↓
┌──────────────────────┐
│ Agent 生成回复 │
│ "找到 5 个文件..." │
└──────────────────────┘
本章小结
- Tool:Agent 的能力扩展,让 Agent 能够执行具体操作
- Backend:文件系统操作的抽象接口,提供统一的文件操作能力
- LocalBackend:Backend 的本地文件系统实现,直接访问操作系统文件系统
- DeepAgent:预构建的高级 Agent,提供 Backend 和 StreamingShell 的一级配置
- 自动注册工具:配置 Backend 后自动注册文件系统工具
- Tool 调用流程:Agent 分析意图 → 生成 Tool Call → 执行 Tool → 返回结果 → 生成回复
扩展思考
其他 Tool 类型:
- HTTP Tool:调用外部 API
- Database Tool:查询数据库
- Calculator Tool:执行计算
- Code Executor Tool:运行代码
其他 Backend 实现:
- 可以基于 Backend 接口实现其他存储后端
- 例如:云存储、数据库存储等
- LocalBackend 已经提供了完整的文件系统操作能力
自定义 Tool 创建:
如果需要创建自定义 Tool,可以使用 utils.InferTool 从函数自动推断。详见: