AI大模型落地系列:一文读懂 Eino 的 Tool 和文件系统访问
声明本文数据源于官方文档与官方实现[Eino]。如何让你的 Eino Agent 长出双手一文读懂 Tool 和文件系统访问1. 为什么跑通 ChatModel 以后你的 Agent 还是只会聊天2. Tool 到底是什么3. 如何为Agent装上操作文件系统能力4. 啥是DeepAgent先了解何为adk水道渠成的deepAgentChatModelAgent与DeepAgent区别5. 跑通一个小 Demo6. 一次 Tool 调用在 Eino 里到底怎么走7. 一分钟复盘参考资料上一篇我们把 Eino 的ChatModel和Message跑通了。但很多人到这一步会误以为自己已经摸到了 Agent 开发的门槛。其实没有。因为会对话不等于会执行。一个只能生成文本的 Agent在工程上还远远谈不上“能干活”。真正的分水岭往往是Tool。上一篇解决模型调用边界这一篇解决执行能力边界。放在 Eino 里这个执行能力最直接的落点就是给Agent接上Tool、接上文件系统、接上DeepAgent。如果还停留在“输入一段 prompt输出一段文本”那你写出来的东西更像一个高级聊天框而不是一个真正能落地的 Agent。1. 为什么跑通 ChatModel 以后你的 Agent 还是只会聊天很多 Go 后端工程师第一次接 Eino最容易产生一个错觉“我已经能把模型调通了也能拿到回复了那我是不是已经在做 Agent 了”这话只对了一半。ChatModel解决的是“怎么和模型说话”Message解决的是“上下文怎么表达”。但这两个边界打通之后你得到的本质上还是一个只能生成文本的能力。它能回答问题。它能续写内容。它甚至能看起来像是在“思考”。但它依然读不了文件查不了目录访问不了外部资源执行不了真实动作很多所谓的 Agent 项目本质上只是把 ChatModel 外面再包了一层壳。这就像什么像你写了一个返回 JSON 的接口但接口后面没连数据库、没连缓存、没连业务系统。它当然“能响应”但你很难说它真的“有业务能力”。所以继上一篇文章之后ChatModel真正该补上的不是更花哨的编排而是让模型先有能力碰到外部世界。而这个入口就是Tool。2. Tool 到底是什么很多人一看到Tool这个词会下意识把它理解成“插件”。这个理解不算错但还不够准。在 Eino 里Tool更像一层统一的外部能力声明。模型不需要知道你的文件读取逻辑怎么写、shell 怎么执行、数据库怎么连它只需要知道这个工具叫什么它是干什么的它收什么参数它调用后会返回什么结果从职责上看可以简单分成三层BaseTool提供工具元信息让模型知道“这里有个工具可用”InvokableTool一次性执行工具输入通常是 JSON 参数输出是字符串结果StreamableTool流式执行工具适合 shell 这类会持续返回内容的场景// BaseTool 提供工具的元信息,ChatModel 使用这些信息决定是否以及如何调用工具typeBaseToolinterface{Info(ctx context.Context)(*schema.ToolInfo,error)}// InvokableTool 是可以被 ToolsNode 执行的工具typeInvokableToolinterface{BaseTool// InvokableRun 执行工具,参数是 JSON 编码的字符串,返回字符串结果InvokableRun(ctx context.Context,argumentsInJSONstring,opts...Option)(string,error)}// StreamableTool 是 InvokableTool 的流式变体typeStreamableToolinterface{BaseTool// StreamableRun 流式执行工具,返回 StreamReaderStreamableRun(ctx context.Context,argumentsInJSONstring,opts...Option)(*schema.StreamReader[string],error)}对模型而言Tool 不是一段代码而是一份可以被选择调用的说明书协议。这也是为什么 Tool 会成为 Agent 和普通聊天程序之间的分水岭。模型一旦具备 Tool Calling它就不再只能“说”而是可以“先调工具再组织答案”。3. 如何为Agent装上操作文件系统能力如果你是做 ChatWithDoc、代码问答、项目助手这类场景最可信的资料是什么不是二手教程。不是群聊截图。也不是别人写的“速通笔记”。最可信的其实是项目自己的源码、注释和示例。这也是为什么这将成为Agent的一次飞跃性进步。因为一旦 Agent 能读目录、读文件、grep 搜索、按 glob 查找它就第一次具备了“自己去找依据”的能力。如果 Agent 连文件都读不了它通常还没从“聊天程序”跨进“执行程序”。这里会出现两个容易混的概念。第一Backend。它是文件系统操作的抽象层负责定义“列目录、读文件、搜索、写入、编辑”这些能力。第二LocalBackend。它是Backend的本地实现直接访问你机器上的文件系统。你可以把它理解成Eino 没有把“读文件”硬编码在 Agent 里而是先抽象成 Backend再给出一个本地版实现。importlocalbkgithub.com/cloudwego/eino-ext/adk/backend/localbackend,err:localbk.NewBackend(ctx,localbk.Config{})之所以这样设计。是因为今天你读的是本地目录明天就可能换成别的存储后端。抽象先顶上能力才有复用空间。另外LocalBackend还有一个特别值得注意的点文件系统工具最好使用绝对路径。4. 啥是DeepAgent咱们先不谈其他你先看看这些import导入的包。import(github.com/cloudwego/eino/adkgithub.com/cloudwego/eino/adk/prebuilt/deepgithub.com/cloudwego/eino/schema)先了解何为adkadk可以理解为 Eino 里专门面向 Agent 的基础开发层。你可以认为他是一套针对底层封装好的接口。它把 Agent 运行所需的一套底层抽象、接口、事件流和执行机制先封装好然后对上层的 Agent 实现和业务代码提供统一能力。水道渠成的deepAgentgithub.com/cloudwego/eino/adk/prebuilt/deep则是建立在 adk 之上的一个开箱即用的预置 Agent 实现官方叫 DeepAgents。官方文档也明确说了它是在 ChatModelAgent 基础上实现的一种现成 agent 方案你不用自己从零拼提示词、工具和上下文管理就能直接得到一个可运行的 Agent。官方表述DeepAgent的优势在于它把文件系统、命令执行和任务能力抬成了一等配置。你不需要从零拼每一个螺丝直接把Backend和StreamingShell传进去它就能把相关工具接起来。注所谓的一等配置就是能直接在Config中配置的参数ChatModelAgent与DeepAgent区别能力ChatModelAgentDeepAgent多轮对话支持支持自定义 Tool需要手动逐个注册可以手动注册也可以接一级配置文件系统访问需要自己创建并注册相关 Tool配置Backend后自动接入命令执行需要自己额外接入配置StreamingShell后自动接入内置任务管理无默认带write_todos子 Agent 能力无支持这里最重要的结论其实就一句纯对话场景用ChatModelAgent一旦要接文件系统、命令执行、任务规划就切DeepAgent官方第四章明确给出了这一组自动注册工具read_filewrite_fileedit_fileglobgrepexecute所以很多 Agent 项目真正的第一步不是上 Workflow而是先把 Tool 接进去先为你的大模型接上双手。5. 跑通一个小 Demo本demo将会使用LocalBackendDeepAgent千问大模型你将会使 “Agent 第一次碰到外部世界”。同样先准备依赖和环境变量go mod init eino-ch04-demo go get github.com/cloudwego/einolatest go get github.com/cloudwego/eino-ext/components/model/qwenlatest go get github.com/cloudwego/eino-ext/adk/backend/locallatestexportDASHSCOPE_API_KEY你的百炼 API KeyexportQWEN_MODELqwen3.5-flashexportPROJECT_ROOT/path/to/your/project如果你在 Windows PowerShell 下环境变量改成$env:DASHSCOPE_API_KEY你的百炼 API Key$env:QWEN_MODELqwen3.5-flash$env:PROJECT_ROOTD:\\your\\project如果不设置PROJECT_ROOT上面这份代码会默认使用当前工作目录。然后把下面这份代码保存成main.gopackagemainimport(contexterrorsfmtiologospath/filepathstringslocalbkgithub.com/cloudwego/eino-ext/adk/backend/localgithub.com/cloudwego/eino-ext/components/model/qwengithub.com/cloudwego/eino/adkgithub.com/cloudwego/eino/adk/prebuilt/deepgithub.com/cloudwego/eino/schema)funcmain(){ctx:context.Background()projectRoot:envOrDefault(PROJECT_ROOT,.)projectRoot,err:filepath.Abs(projectRoot)iferr!nil{log.Fatalf(resolve project root failed: %v,err)}cm,err:qwen.NewChatModel(ctx,qwen.ChatModelConfig{BaseURL:https://dashscope.aliyuncs.com/compatible-mode/v1,APIKey:mustEnv(DASHSCOPE_API_KEY),Model:envOrDefault(QWEN_MODEL,qwen3.5-flash),})iferr!nil{log.Fatalf(new qwen chat model failed: %v,err)}backend,err:localbk.NewBackend(ctx,localbk.Config{})iferr!nil{log.Fatalf(new local backend failed: %v,err)}instruction:fmt.Sprintf(你是一个专业的 Eino 助手。 当你调用文件系统工具时必须使用绝对路径。 项目根目录是%s 如果用户说“当前目录”默认指 %s。,projectRoot,projectRoot)agent,err:deep.New(ctx,deep.Config{Name:Ch04ToolAgent,Description:A minimal Eino agent with filesystem access.,ChatModel:cm,Instruction:instruction,Backend:backend,StreamingShell:backend,MaxIteration:20,})iferr!nil{log.Fatalf(new deep agent failed: %v,err)}query:请列出当前目录下的 Go 文件并读取 main.go 的前 20 行iflen(os.Args)1{querystrings.Join(os.Args[1:], )}runner:adk.NewRunner(ctx,adk.RunnerConfig{Agent:agent,EnableStreaming:true,})events:runner.Run(ctx,[]*schema.Message{schema.UserMessage(query),})iferr:printEvents(events);err!nil{log.Fatalf(run agent failed: %v,err)}}// printEvents 不断消费 Agent 运行产生的事件流// 把助手回复、工具调用、工具结果按可读方式打印到终端。funcprintEvents(events*adk.AsyncIterator[*adk.AgentEvent])error{for{event,ok:events.Next()if!ok{returnnil}ifevent.Err!nil{returnevent.Err}ifevent.Outputnil||event.Output.MessageOutputnil{continue}// 实际输出mv:event.Output.MessageOutputifmv.Roleschema.Tool{content,err:drainMessageVariant(mv)iferr!nil{returnerr}fmt.Printf([tool result]\n%s\n\n,content)continue}ifmv.Role!schema.Assistantmv.Role!{continue}ifmv.IsStreamingmv.MessageStream!nil{mv.MessageStream.SetAutomaticClose()vartoolCalls[]schema.ToolCallfor{frame,err:mv.MessageStream.Recv()iferrors.Is(err,io.EOF){break}iferr!nil{returnerr}ifframenil{continue}ifframe.Content!{fmt.Print(frame.Content)}iflen(frame.ToolCalls)0{toolCallsappend(toolCalls,frame.ToolCalls...)}}fmt.Println()for_,tc:rangetoolCalls{fmt.Printf([tool call] %s(%s)\n,tc.Function.Name,tc.Function.Arguments)}continue}ifmv.Message!nil{fmt.Println(mv.Message.Content)}}}// 拼接成完整string在返回funcdrainMessageVariant(mv*adk.MessageVariant)(string,error){ifmv.Message!nil{returnmv.Message.Content,nil}if!mv.IsStreaming||mv.MessageStreamnil{return,nil}varsb strings.Builderfor{chunk,err:mv.MessageStream.Recv()iferrors.Is(err,io.EOF){break}iferr!nil{return,err}ifchunk!nilchunk.Content!{sb.WriteString(chunk.Content)}}returnsb.String(),nil}funcmustEnv(keystring)string{v:os.Getenv(key)ifv{log.Fatalf(%s is empty,key)}returnv}funcenvOrDefault(key,fallbackstring)string{ifv:os.Getenv(key);v!{returnv}returnfallback}直接执行go run.--请列出当前目录下的 Go 文件并读取 main.go 的前 20 行你会看到控制台里先出现tool call然后出现tool result最后才是模型整理后的自然语言回复。这一步非常关键。因为它说明 Agent 已经不是“凭空回答”而是在先找依据再组织答案。6. 一次 Tool 调用在 Eino 里到底怎么走当用户说“列出当前目录的文件并读取 main.go”时Eino 里发生的大致是这件事用户提问 - 模型判断这不是纯文本回答能解决的问题 - 生成 tool call(JSON 参数) - DeepAgent 把调用路由到对应 Tool - Backend/LocalBackend 真正执行文件系统操作 - tool result 回到上下文 - 模型基于结果生成最终回答这条链一旦跑通你对 Agent 的理解就会发生变化。不是“模型突然变聪明了”而是模型负责理解问题和决定要不要调工具Tool 负责提供能力入口Backend 负责把动作真正落到外部世界Agent 负责把这一切串起来这也是为什么DeepAgent比“单纯会聊天的 ChatModel”更接近工程里的执行型 Agent。7. 一分钟复盘如果你读完这篇希望你能收获这些ChatModel解决的是模型调用边界不是执行能力边界Tool是 Agent 第一次真正碰到外部世界的入口文件系统能力之所以重要是因为源码、注释、示例本身就是最可信的知识源纯对话继续用ChatModelAgent一旦要接文件系统和命令执行就该切到DeepAgent参考资料Eino 第四章Tool 与文件系统访问https://www.cloudwego.io/zh/docs/eino/quick_start/chapter_04_tool_and_filesystem/Eino DeepAgents 文档https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/agent_implementation/deepagents/Eino 官方示例cmd/ch04/main.gohttps://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch04/main.go

相关新闻

最新新闻

日新闻

周新闻

月新闻