--- tags: ["Eino", "Agent", "Tool", "Backend", "DeepAgent", "文件系统"] create time: "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 中定义可执行能力的接口: ```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`:**信息层**,只提供工具的名称、描述和参数 schema,ChatModel 据此决定是否调用 - `InvokableTool`:**同步执行层**,返回完整结果字符串,适合大多数文件操作场景 - `StreamableTool`:**流式执行层**,通过 `StreamReader` 逐步输出结果,适合大量输出的场景(如长文件读取或命令执行) > [!note] 设计要点 > > 为什么 Tool 的参数是 JSON 字符串而不是 Go struct? > 因为 Tool 需要在 ChatModel 的上下文里传递——模型只能理解文本。所以 Eino 将参数序列化为 JSON 传给模型,模型再返回 JSON,由 ToolsNode 反序列化后调用底层实现。这种设计让任何语言/协议的工具都能与基于 JSON 的大模型对齐。 ### 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 } ``` > [!tip] 方法分组 > > Backend 的六类方法可以归为三类: > **发现**(LsInfo / GlobInfo)、**检索**(Read / GrepRaw)、**修改**(Write / Edit)。记住这个分类有助于快速理解不同 Backend 实现的侧重点。 ### 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) - 路径安全:要求使用绝对路径,防止目录遍历攻击 - 零配置:开箱即用,无需额外设置 > [!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? 相比直接使用 ChatModelAgent,DeepAgent 的优势在于把「基础设施」封装成了第一类配置,你只需声明意图而非实现细节: 1. **一级配置**:Backend 和 StreamingShell 直接在 Config 中传入,无需自己组装 ToolsNode 2. **自动注册工具**:配置 Backend 后自动注册文件系统工具,免去逐个定义的样板代码 3. **内置任务管理**:提供 `write_todos` 工具,支持复杂任务的规划与跟踪 4. **支持子 Agent**:可以配置专门的子 Agent 处理特定任务 5. **更强大**:集成了文件系统、命令执行等多种能力 ### 代码实现 这段代码完成了两件事:创建 Backend 实例,再将其注入 DeepAgent。让我们逐行看: ```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{}) // 第二步:将 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 自动注册的工具 当配置了 `Backend` 和 `StreamingShell` 后,DeepAgent 会自动注册以下工具: | 工具名 | 对应 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。 ## 代码位置 - 入口代码:[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 的完整使用场景。 **推荐的三仓库目录结构(如要完整体验):** ```mermaid 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` 脚本自动设置上述目录结构: ```bash # 在 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 时,内部会经历以下循环。你可以把整个过程理解为「思考 → 行动 → 观察」的闭环: ```mermaid 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 // 写一个普通 Go 函数... func Greet(name string) string { return fmt.Sprintf("Hello, %s!", name) } // ...一行代码转成 Tool greetTool := utils.InferTool(Greet) ``` 如果你想了解更多,详见: - [Tool 接口文档](https://github.com/cloudwego/eino/tree/main/components/tool) - [Tool 创建示例](https://github.com/cloudwego/eino-examples/tree/main/components/tool) ## 关联笔记 - [[Eino/quick_start/_index]] - [[Eino/quick_start/chapter_03_memory_and_session]] — Agent 的记忆与会话管理(上一章) - [[Eino/quick_start/chapter_05_middleware]] — Tool 调用中间件与拦截器(下一章)