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.

11 KiB

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 中定义可执行能力的接口:

// 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 的一级配置,可以方便地注册文件系统相关的工具。

为什么使用 DeepAgent?

相比直接使用 ChatModelAgent,DeepAgent 的优势:

  1. 一级配置: Backend 和 StreamingShell 是一级配置,直接传入即可
  2. 自动注册工具: 配置 Backend 后自动注册文件系统工具,无需手动创建
  3. 内置任务管理: 提供 write_todos 工具,支持任务规划和跟踪
  4. 支持子 Agent: 可以配置专门的子 Agent 处理特定任务
  5. 更强大: 集成了文件系统、命令执行等多种能力

代码实现

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 自动注册的工具

当配置了 BackendStreamingShell 后,DeepAgent 会自动注册以下工具:

  • read_file: 读取文件内容
  • write_file: 写入文件内容
  • edit_file: 编辑文件内容
  • glob: 根据 glob 模式查找文件
  • grep: 在文件中搜索内容
  • execute: 执行 shell 命令

代码位置

前置条件

与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)

运行

examples/quickstart/chatwitheino 目录下执行:

# 设置 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 脚本来快速设置:

# 在 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 框架的完整代码库,包括核心库、扩展库和示例库,为用户提供全面的技术支持。

输出示例:

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 从函数自动推断。详见: