You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
title: "第四章: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 中定义可执行能力的接口:
```go
// 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 中用于文件系统操作的抽象接口:
```go
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 的本地文件系统实现,直接访问操作系统的文件系统:
```go
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 的优势:
1. **一级配置**: Backend 和 StreamingShell 是一级配置,直接传入即可
2. **自动注册工具**: 配置 Backend 后自动注册文件系统工具,无需手动创建
3. **内置任务管理**: 提供 `write_todos` 工具,支持任务规划和跟踪
4. **支持子 Agent**: 可以配置专门的子 Agent 处理特定任务
5. **更强大**: 集成了文件系统、命令执行等多种能力
### 代码实现
```go
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](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch04/main.go)
## 前置条件
与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
本章还需要设置 `PROJECT_ROOT`(可选,见下方运行说明)。
## 运行
`examples/quickstart/chatwitheino` 目录下执行:
```bash
# 可选:设置 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_ROOTEino 核心库)
├── adk/
├── components/
├── compose/
├── ext/ # eino-ext扩展组件如 OpenAI、Ark 等实现)
├── examples/ # eino-examples本仓库本示例所在位置
│ └── quickstart/
│ └── chatwitheino/
└── ...
```
可以使用 `dev_setup.sh` 脚本自动设置上述目录结构:
```bash
# 在 eino 根目录运行,自动克隆扩展库和示例库到正确位置
bash scripts/dev_setup.sh
```
输出示例:
```text
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` 从函数自动推断。详见:
- [Tool 接口文档](https://github.com/cloudwego/eino/tree/main/components/tool)
- [Tool 创建示例](https://github.com/cloudwego/eino-examples/tree/main/components/tool)