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.
312 lines
11 KiB
Markdown
312 lines
11 KiB
Markdown
---
|
|
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 的一级配置,可以方便地注册文件系统相关的工具。
|
|
|
|
### 为什么使用 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/blob/main/examples/quickstart/chatwitheino/cmd/ch04/main.go)
|
|
|
|
## 前置条件
|
|
|
|
与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)
|
|
|
|
## 运行
|
|
|
|
在 `examples/quickstart/chatwitheino` 目录下执行:
|
|
|
|
```bash
|
|
# 设置 Eino 核心库的根目录路径
|
|
# 这是 Agent 能够访问的代码库范围,Agent 将检索和读取该目录下的代码和文档
|
|
export PROJECT_ROOT=/path/to/eino
|
|
|
|
go run ./cmd/ch04
|
|
```
|
|
|
|
**重要说明:**
|
|
|
|
`PROJECT_ROOT` 环境变量指定了 Eino 核心库在文件系统中的路径。因为本示例是 ChatWithDoc(与文档对话),Agent 需要知道 Eino 项目的代码在哪里,才能检索和读取代码及文档。
|
|
|
|
**三个仓库的相对路径关系:**
|
|
|
|
```
|
|
PROJECT_ROOT/ # Eino 核心库根目录 (由 PROJECT_ROOT 指定)
|
|
├── adk/ # Agent Development Kit
|
|
├── components/ # 核心组件
|
|
├── compose/ # 编排组件
|
|
├── examples/ # 示例代码
|
|
│ └── quickstart/ # 快速开始示例
|
|
│ └── chatwitheino/ # 本示例所在位置
|
|
└── ...
|
|
```
|
|
|
|
**依赖关系:**
|
|
|
|
- **eino**: 核心库,由 `PROJECT_ROOT` 指定路径
|
|
- **eino-ext**: 扩展库,需要在 `PROJECT_ROOT/ext` 目录下(扩展组件实现,如 OpenAI、Ark 等)
|
|
- **eino-examples**: 示例代码库,需要在 `PROJECT_ROOT/examples` 目录下
|
|
|
|
**重要**: 上述相对路径关系需要用户手动保证,不是自动配置的。可以使用 `dev_setup.sh` 脚本来快速设置:
|
|
|
|
```bash
|
|
# 在 eino 根目录运行脚本,自动克隆扩展库和示例库到正确位置
|
|
bash scripts/dev_setup.sh
|
|
```
|
|
|
|
脚本会创建以下目录结构:
|
|
```
|
|
eino/ # PROJECT_ROOT (核心库)
|
|
├── ext/ # eino-ext (扩展组件)
|
|
├── examples/ # eino-examples (示例代码)
|
|
└── ... core framework
|
|
```
|
|
|
|
**为什么需要设置 PROJECT_ROOT?**
|
|
|
|
`PROJECT_ROOT` 是 Eino 核心库的文件系统路径,通过这一个配置就能实现 Eino 框架全量的代码检索:
|
|
|
|
1. **核心库检索**: `PROJECT_ROOT` 指向 Eino 核心库,Agent 可以直接读取核心代码
|
|
2. **扩展库检索**: 根据 `PROJECT_ROOT` 可以定位到 `eino-ext` 仓库(扩展组件实现)
|
|
3. **示例库检索**: 根据 `PROJECT_ROOT` 可以定位到 `eino-examples` 仓库(示例代码)
|
|
|
|
**一个配置,全量检索** - 通过设置 `PROJECT_ROOT`,Agent 能够访问 Eino 框架的完整代码库,包括核心库、扩展库和示例库,为用户提供全面的技术支持。
|
|
|
|
输出示例:
|
|
|
|
```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)
|