Files
CamTalk/docs/Eino/quick_start/chapter_04_tool_and_filesystem.md

16 KiB
Raw Blame History

tags, create time
tags create time
Eino
Agent
Tool
Backend
DeepAgent
文件系统
2026-04-29 15:30

第四章Tool 与文件系统访问

概述

本章为 Agent 引入 Tool工具能力使其能够突破纯文本对话的边界直接操作文件系统、搜索代码库、执行命令。通过 DeepAgent 预构建组件和 Backend 抽象接口,只需几行配置即可让 Agent「看见」并「触碰」真实世界。

为什么需要 Tool

前三章我们实现的 Agent 只能对话,无法执行实际操作。

Agent 的局限:

  • 只能生成文本回复
  • 无法访问外部资源(文件、API、数据库等)
  • 无法执行实际任务(计算、查询、修改等)

Tool 的定位:

  • Tool 是 Agent 的能力扩展:让 Agent 能够执行具体操作
  • Tool 封装了具体实现:Agent 不关心 Tool 内部如何工作,只关心输入输出
  • Tool 可组合:一个 Agent 可以有多个 Tool,根据需要选择调用

简单类比:

  • Agent = "智能助手"(能理解指令,但需要工具才能执行)
  • Tool = "工具箱"(文件操作、网络请求、数据库查询等)

[!question] 深入思考

如果 Agent 拥有无限个 Tool会不会反而变得更差
提示考虑模型上下文窗口限制、Token 成本、以及"选择困难症"效应。实际设计中,工具的元信息描述质量比数量更重要——一个好的 Description 能让模型精准选对工具。

为什么需要文件系统能力

本示例是 ChatWithDoc与文档对话目标是帮助用户学习 Eino 框架并编写 Eino 代码。那么,最好的文档是什么?

答案就是Eino 仓库的代码本身。

资源类型 价值
Code 源代码展示了框架的真实实现
Comment 代码注释提供了设计思路和使用说明
Examples 示例代码演示了最佳实践

通过文件系统访问能力Agent 可以直接读取 Eino 源码、注释和示例,为用户提供最准确、最及时的技术支持。

[!tip] 现实启发

很多官方文档会过时或写得模糊,但代码不会。让 Agent "读源码"是一种绕过信息衰减的可靠策略——这也是为什么 RAG 系统的向量库经常直接索引代码仓库的原因。

关键概念

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信息层,只提供工具的名称、描述和参数 schemaChatModel 据此决定是否调用
  • InvokableTool同步执行层,返回完整结果字符串,适合大多数文件操作场景
  • StreamableTool流式执行层,通过 StreamReader 逐步输出结果,适合大量输出的场景(如长文件读取或命令执行)

[!note] 设计要点

为什么 Tool 的参数是 JSON 字符串而不是 Go struct
因为 Tool 需要在 ChatModel 的上下文里传递——模型只能理解文本。所以 Eino 将参数序列化为 JSON 传给模型,模型再返回 JSON由 ToolsNode 反序列化后调用底层实现。这种设计让任何语言/协议的工具都能与基于 JSON 的大模型对齐。

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
}

[!tip] 方法分组

Backend 的六类方法可以归为三类: 发现LsInfo / GlobInfo检索Read / GrepRaw修改Write / Edit。记住这个分类有助于快速理解不同 Backend 实现的侧重点。

LocalBackend

LocalBackend 是 Backend 的本地文件系统实现,直接访问操作系统的文件系统:

import localbk "github.com/cloudwego/eino-ext/adk/backend/local"

backend, err := localbk.NewBackend(ctx, &localbk.Config{})

特点:

  • 直接访问本地文件系统,使用 Go 标准库实现
  • 支持所有 Backend 接口方法
  • 支持执行 shell 命令ExecuteStreaming
  • 路径安全:要求使用绝对路径,防止目录遍历攻击
  • 零配置:开箱即用,无需额外设置

[!warning] 安全问题

LocalBackend 要求使用绝对路径来防止 ../ 目录遍历攻击。这意味着 Agent 永远不能通过构造恶意文件名跳出设定的根目录范围——这是 LLM 集成中的关键安全措施。

实现:使用 DeepAgent

本章使用 DeepAgent 预构建 Agent,它提供了 Backend 和 StreamingShell 的一级配置,可以方便地注册文件系统相关的工具。

从 ChatModelAgent 到 DeepAgent何时需要切换

前面章节一直使用 ChatModelAgent,它已经能处理多轮对话。但要访问文件系统,我们需要切换到 DeepAgent

ChatModelAgent vs DeepAgent 对比:

能力ChatModelAgentDeepAgent
多轮对话
添加自定义 Tool 手动注册每个 Tool 手动注册或自动注册
文件系统访问Backend 需手动创建并注册所有文件工具 一级配置,自动注册
命令执行StreamingShell 需手动创建 一级配置,自动注册
内置任务管理 `write_todos` 工具
支持子 Agent

[!tip] 选择建议

  • 纯对话场景(无外部访问)→ 用 ChatModelAgent
  • 需要访问文件系统或执行命令 → 用 DeepAgent

为什么使用 DeepAgent?

相比直接使用 ChatModelAgentDeepAgent 的优势在于把「基础设施」封装成了第一类配置,你只需声明意图而非实现细节:

  1. 一级配置Backend 和 StreamingShell 直接在 Config 中传入,无需自己组装 ToolsNode
  2. 自动注册工具:配置 Backend 后自动注册文件系统工具,免去逐个定义的样板代码
  3. 内置任务管理:提供 write_todos 工具,支持复杂任务的规划与跟踪
  4. 支持子 Agent:可以配置专门的子 Agent 处理特定任务
  5. 更强大:集成了文件系统、命令执行等多种能力

代码实现

这段代码完成了两件事:创建 Backend 实例,再将其注入 DeepAgent。让我们逐行看

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{})

// 第二步:将 backend 注入 DeepAgent它会自动注册文件相关 Tool
agent, err := deep.New(ctx, &deep.Config{
	Name:           "Ch04ToolAgent",            // Agent 的名称
	Description:    "ChatWithDoc agent with filesystem access.",
	ChatModel:      cm,                         // 底层大模型
	Instruction:    instruction,                // 系统提示词
	Backend:        backend,                    // 文件系统操作能力
	StreamingShell: backend,                    // 命令执行能力
	MaxIteration:   50,                         // 最大思考-行动循环次数
})

[!note] MaxIteration 的含义

每次 Agent 「思考 → Tool Call → 观察结果」算一次迭代。设为 50 意味着最多允许 50 轮。如果达到上限仍未得到满意结果,会返回部分完成的内容。设置过高会浪费 Token过低则可能让 Agent 中途放弃。后续章节会讨论如何优化这个值。

DeepAgent 自动注册的工具

当配置了 BackendStreamingShellDeepAgent 会自动注册以下工具:

工具名 对应 Backend 方法 用途
read_file Read 读取文件内容
write_file Write 写入文件内容
edit_file Edit 编辑文件内容
glob GlobInfo 根据 glob 模式查找文件
grep GrepRaw 在文件中搜索内容
execute shell 命令 执行 shell 命令

[!note] 从接口到 Tool 的映射

Backend 定义了 6 个方法LsInfo / Read / GrepRaw / GlobInfo / Write / Edit但 DeepAgent 只注册了 5 个 Tool少了 LsInfo。这是因为 glob 已经能完成目录探索的需求。如果未来需要更详细的列表信息,可以手动补充 LsInfo 对应的 Tool。

代码位置

前置条件

与第一章一致:需要配置一个可用的 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 的完整使用场景。

推荐的三仓库目录结构(如要完整体验):

graph LR
    ER[eino 核心库\nPROJECT_ROOT] --> ADK[adk/]
    ER --> COMP[components/]
    ER --> COMPOSE[compose/]
    ER --> EXT[eino-ext 扩展库]
    ER --> EX[eino-examples 示例库]
    EX --> QS[quickstart/]
    QS --> CW[chatwitheino / 本示例]

    style ER fill:#e3f2fd
    style CW fill:#fff3e0

[!example] PROJECT_ROOT 的两种用法

场景一:快速验证(不设置)
Agent 只能访问 chatwitheino 目录下的文件,适合学习当前示例的代码结构。

场景二:完整对话(设置 export PROJECT_ROOT=/path/to/eino
Agent 可以搜索 Eino 框架全部源码——比如查找某个 API 的实现细节、阅读组件设计思路等。这是 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 时,内部会经历以下循环。你可以把整个过程理解为「思考 → 行动 → 观察」的闭环:

flowchart LR
    U[用户提问] --> A{Agent\n分析意图}
    A -->|纯对话| R[直接回复]
    A -->|需要工具| T1[生成 Tool Call\n参数 JSON]
    T1 --> T2[执行 Tool\n读取/写入/搜索等]
    T2 --> T3[返回 Tool Result]
    T3 --> A2{Agent\n整合信息}
    A2 --> R2[生成最终回复]
    R2 --> U2[回复用户]

    style A fill:#e1f5fe
    style T2 fill:#fff3e0
    style T3 fill:#e8f5e9

以"列出当前目录的文件"为例:

[!example] 逐步拆解

Step 1 — 意图识别
用户说"列出文件"Agent 判断这不是纯对话请求,而是文件系统操作意图。

Step 2 — 工具选择与参数生成
Agent 从可用工具中选择 glob(查找文件列表),生成参数 {"pattern": "*"}

Step 3 — Tool 执行
ToolsNode 接收 JSON 参数,反序列化后调用 GlobInfo,将结果序列化为 JSON 返回。

Step 4 — 结果整合
Agent 收到 ["main.go", "go.mod", ...],整理成自然语言回复:"找到 5 个文件..."。

你是否有想过——Agent 是如何知道该选哪个工具的?关键在于 BaseTool.Info() 返回的 元信息(名称、描述、参数 schema。ChatModel 基于这些信息来决定调用哪个 Tool 以及传入什么参数。这就是后面会详细展开的 Function Calling 机制。

本章小结

概念 一句话理解
Tool Agent 的能力扩展,让它能执行具体操作而非仅对话
Backend 文件系统操作的抽象接口,定义发现、检索、修改三类方法
LocalBackend Backend 的本地实现,开箱即用且路径安全
DeepAgent 预构建的高级 Agent配置 Backend 即可自动获得文件能力
自动注册 声明式配置优于命令式组装——传一个 Backend 就得到五个工具
调用流程 意图分析 → Tool Call → 执行 → 结果 → 回复(闭环迭代)

扩展思考

其他 Tool 类型

当前我们只用了文件系统相关 Tool。Eino 生态中还支持更多类型:

  • HTTP Tool:调用外部 API让 Agent 具备联网能力
  • Database Tool:查询数据库,适用于数据分析场景
  • Calculator Tool:精确计算,弥补大模型不擅长数学的问题
  • Code Executor Tool:运行代码,适合生成并验证算法实现

自定义 Tool 创建

除了使用 DeepAgent 自动注册的 Tool你还可以手动创建。最简单的方式是使用 utils.InferTool 从函数签名自动推断参数 schema

// 写一个普通 Go 函数...
func Greet(name string) string {
	return fmt.Sprintf("Hello, %s!", name)
}

// ...一行代码转成 Tool
greetTool := utils.InferTool(Greet)

如果你想了解更多,详见:

关联笔记