【Eino 框架入门】Backend 是怎么变成工具的

【Eino 框架入门】Backend 是怎么变成工具的

上一篇我们这样创建 Agent:

go 复制代码
backend, _ := localbk.NewBackend(ctx, &localbk.Config{})
agent, _ := deep.New(ctx, &deep.Config{
    Backend:        backend,
    StreamingShell: backend,
})

传了个 Backend 进去,Agent 就有了 read_filegrep 等工具。

问题是:Backend 是怎么变成工具的?

这篇文章跟踪一个工具的诞生过程:从 Backend 配置到模型能调用的工具。

一个工具的诞生:read_file

我们以 read_file 工具为例,看它从无到有的完整过程。

css 复制代码
你写的代码                    框架做的事
─────────────────────────────────────────────────
Backend: backend       →     filesystem middleware
                              ↓
                         toolSpec{createFunc: newReadFileTool}
                              ↓
                         utils.InferTool(readFileArgs)
                              ↓
                         ToolInfo{read_file, 参数schema}
                              ↓
                         模型看到工具说明书

下面一步步拆解。

第一步:Backend 进入 middleware

deep.New() 收到你的配置后,会创建一个 filesystem middleware:

go 复制代码
// adk/prebuilt/deep/deep.go
func buildBuiltinAgentMiddlewares(ctx context.Context, cfg *Config) {
    if cfg.Backend != nil {
        fm, _ := filesystem.New(ctx, &filesystem.MiddlewareConfig{
            Backend:        cfg.Backend,        // 你的 Backend
            StreamingShell: cfg.StreamingShell, // 你的 StreamingShell
        })
        // fm 就是一个 middleware,里面已经包含了工具
    }
}

Backend 就这样被传进去了。接下来看 middleware 内部发生了什么。

第二步:toolSpec 模式创建工具

middleware 调用 getFilesystemTools() 生成工具列表。

这里用了一个设计模式:toolSpec(工具规格)

go 复制代码
// adk/middlewares/filesystem/filesystem.go
type toolSpec struct {
    config     *ToolConfig  // 可选:自定义名称、描述、禁用
    createFunc func(name, desc string) (tool.BaseTool, error)
}

每个工具用一个 toolSpec 描述。比如 read_file

go 复制代码
toolSpecs := []toolSpec{
    {
        config: cfg.ReadFileToolConfig,  // 用户可配置
        createFunc: func(name, desc string) (tool.BaseTool, error) {
            if cfg.Backend != nil {
                return newReadFileTool(cfg.Backend, name, desc)
            }
            return nil, nil
        },
    },
    // ls, write_file, grep 等同理...
}

// 统一遍历创建
for _, spec := range toolSpecs {
    t, _ := createToolFromSpec(cfg, spec)
    if t != nil {
        tools = append(tools, t)
    }
}

为什么用 toolSpec?

  • 统一处理:所有工具的创建逻辑一致
  • 可扩展:用户可以通过 ToolConfig 覆盖名称、描述、甚至整个工具

第三步:Go 函数 → Tool + Schema

newReadFileTool() 是核心,它把一个 Go 函数变成模型能理解的工具:

go 复制代码
// adk/middlewares/filesystem/filesystem.go
func newReadFileTool(fs filesystem.Backend, name, desc string) (tool.BaseTool, error) {
    return utils.InferTool("read_file", desc, func(ctx context.Context, input readFileArgs) (string, error) {
        // 这里调用 Backend 的 Read 方法
        fileCt, err := fs.Read(ctx, &filesystem.ReadRequest{
            FilePath: input.FilePath,
            Offset:   input.Offset,
            Limit:    input.Limit,
        })
        // ... 格式化返回
    })
}

关键魔法utils.InferTool() 从结构体 tag 推断 JSON Schema:

go 复制代码
type readFileArgs struct {
    FilePath string `json:"file_path" jsonschema:"description=The path to the file to read"`
    Offset   int    `json:"offset"   jsonschema:"description=The line number to start reading from"`
    Limit    int    `json:"limit"    jsonschema:"description=The number of lines to read"`
}

反射读取 tag,自动生成模型能理解的 Schema:

json 复制代码
{
  "type": "object",
  "properties": {
    "file_path": {"type": "string", "description": "The path to the file to read"},
    "offset": {"type": "integer", "description": "..."},
    "limit": {"type": "integer", "description": "..."}
  },
  "required": ["file_path"]
}

这就是模型看到的"工具说明书"。模型根据这个说明书决定:

  • 要不要调用工具
  • 调用时传什么参数

第四步:工具注入 Agent

工具创建好了,怎么给 Agent?

middleware 有个 BeforeAgent 钩子,在 Agent 运行前执行:

go 复制代码
// adk/middlewares/filesystem/filesystem.go
func (m *filesystemMiddleware) BeforeAgent(ctx context.Context, runCtx *adk.ChatModelAgentContext) {
    nRunCtx := *runCtx
    // 把工具追加到 Agent 的工具列表
    nRunCtx.Tools = append(nRunCtx.Tools, m.additionalTools...)
    return ctx, &nRunCtx
}

然后 Agent 运行时,会:

  1. 调用每个工具的 Info() 方法获取 ToolInfo
  2. 通过 model.WithTools(toolInfos) 把工具信息传给模型
  3. 模型根据工具信息决定是否调用

总结一张图

scss 复制代码
你的代码                    框架处理                       模型看到
─────────────────────────────────────────────────────────────────────
deep.Config{
    Backend: backend    →   filesystem.New()
}                               ↓
                          getFilesystemTools()
                               ↓
                          toolSpec 模式
                               ↓
                          newReadFileTool()
                               ↓
                          utils.InferTool()
                               ↓
                          ToolInfo{              →   "read_file 工具,
                            Name: "read_file"         参数:file_path..."
                            Params: JSON Schema
                          }
                               ↓
                          BeforeAgent() 注入
                               ↓
                          model.WithTools()

核心:Backend 提供文件系统操作能力,框架把它封装成工具,生成 JSON Schema 说明书,模型根据说明书调用。

工具与 Backend 方法的对应

工具 Backend 方法 用途
ls LsInfo() 列目录
read_file Read() 读文件
write_file Write() 写文件
edit_file Edit() 编辑文件
glob GlobInfo() 文件模式匹配
grep GrepRaw() 搜索内容
execute Execute() / ExecuteStreaming() 执行命令

所以你传一个 Backend,框架就帮你生成这 7 个工具。

相关推荐
SimonKing6 分钟前
别让你的代码裸奔!Spring Boot混淆全攻略(附配置)
java·后端·程序员
Mintopia13 分钟前
系统复杂度失控的根源:不是业务,而是边界
后端
穗余14 分钟前
Rust——impl是什么意思
开发语言·后端·rust
代码羊羊17 分钟前
Rust模式匹配
开发语言·后端·rust
IT_陈寒23 分钟前
Python的GIL把我CPU跑满时我才明白并发不是这样玩的
前端·人工智能·后端
小江的记录本25 分钟前
【分布式】分布式系统核心知识体系:CAP定理、BASE理论与核心挑战
java·前端·网络·分布式·后端·python·安全
一个public的class1 小时前
前后端 + Nginx + Gateway + K8s 全链路架构图解
前端·后端·nginx·kubernetes·gateway
callJJ1 小时前
SpringBoot 自动配置原理详解——从“约定优于配置“到源码全程追踪
java·spring boot·后端·spring
小江的记录本1 小时前
【分布式】分布式一致性协议:2PC/3PC、Paxos、Raft、ZAB 核心原理、区别(2026必考Raft)
java·前端·分布式·后端·安全·面试·系统架构
古方路杰出青年1 小时前
学习笔记1:Python FastAPI极简后端API示例解析
笔记·后端·python·学习·fastapi