【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 个工具。

相关推荐
小谢小哥2 小时前
08-Java语言核心-JVM原理-垃圾收集详解
后端
超捻2 小时前
09 python 数据类型 | 列表
后端
SimonKing2 小时前
紧急自查!Apifox被投毒,使用者速看:你的Git、SSH、云密钥可能已泄露
java·后端·程序员
AskHarries2 小时前
他用20年拿下WSBK冠军,而你还没开始做第一个产品
后端
￰meteor3 小时前
23种设计模式 -【抽象工厂】
后端·设计模式
武子康3 小时前
大数据-257 离线数仓 - 数据质量监控详解:从理论到Apache Griffin实践
大数据·hadoop·后端
liangblog3 小时前
Spring Boot中手动实例化 `JdbcTemplate` 并指定 数据源
java·spring boot·后端
羊小猪~~3 小时前
算法/力扣--栈与队列经典题目
开发语言·c++·后端·考研·算法·leetcode·职场和发展
晨非辰3 小时前
Git版本控制速成:提交三板斧/日志透视/远程同步15分钟精通,掌握历史回溯与多人协作安全模型
linux·运维·服务器·c++·人工智能·git·后端