【Eino 框架入门】给 Agent 装上"手"——工具调用

0 阅读3分钟

【Eino 框架入门】给 Agent 装上"手"——工具调用

前几篇 Agent 只能聊天,这篇让它能操作文件系统:读文件、搜代码、列目录。

ch03 vs ch04

ch03ch04
AgentChatModelAgentdeep Agent
能力只能聊天能调用工具
后端LocalBackend

打个比方:ch03 的 Agent 像个只能说话的顾问,ch04 的 Agent 像个能动手的工程师。

核心代码

// 1. 创建本地后端(提供文件系统工具)
backend, _ := localbk.NewBackend(ctx, &localbk.Config{})

// 2. 创建 deep Agent(支持工具调用)
agent, _ := deep.New(ctx, &deep.Config{
    ChatModel:      cm,
    Instruction:    "You are a helpful assistant...",
    Backend:        backend,   // 注入后端,提供工具
    StreamingShell: backend,   // 流式执行工具
    MaxIteration:   50,        // 最多迭代 50 轮
})

// 3. 运行 Agent(和之前一样)
events := runner.Run(ctx, history)

三行关键代码:

  1. NewBackend - 创建工具提供者
  2. deep.New - 创建支持工具的 Agent
  3. Backend: backend - 把工具给 Agent

工具调用流程

用户: "帮我看看 main.go 里有什么"
        ↓
Agent 思考: 需要调用 read_file 工具
        ↓
[tool call] read_file({"file_path": "/path/main.go"})
        ↓
LocalBackend 执行工具,返回文件内容
        ↓
[tool result] {"content": "package main\n\nfunc main() {...}"}
        ↓
Agent 基于结果回复: "main.go 是程序入口,定义了 main 函数..."

Agent 会自动判断:这个问题需要工具吗?需要就调用,不需要就直接回答。

LocalBackend 提供的工具

工具功能示例
ls列目录ls({"path": "/tmp"})
read_file读文件read_file({"file_path": "/tmp/a.txt"})
glob模式匹配glob({"pattern": "**/*.go"})
grep搜索内容grep({"pattern": "func main", "path": "."})

注意:grep 依赖 rg (ripgrep),需要先安装:

brew install ripgrep

deep Agent 是什么

deep 是 Eino 预构建的 Agent,特点:

  1. 自动工具选择:根据用户问题自动决定调用哪个工具
  2. 多轮迭代:调用工具后继续思考,可能再调用其他工具
  3. 流式输出:支持流式返回结果

源码位置:github.com/cloudwego/eino/adk/prebuilt/deep

type Config struct {
    ChatModel      model.BaseChatModel  // 大模型
    Instruction    string               // 系统提示词
    Backend        filesystem.Backend   // 工具后端
    StreamingShell filesystem.StreamingShell  // 流式执行
    MaxIteration   int                  // 最大迭代轮数
}

MaxIteration 控制工具调用循环的最大次数。比如用户问"找出所有 TODO 并整理",Agent 可能需要:

  1. 调用 glob 找到所有 .go 文件
  2. 对每个文件调用 grep 搜索 TODO
  3. 整理结果回复用户

这就是多轮迭代。

事件流的变化

ch04 的事件流比 ch03 多了两种消息:

Tool Call(模型决定调用工具)

// Message.ToolCalls 包含要调用的工具
for _, tc := range mv.Message.ToolCalls {
    fmt.Printf("[tool call] %s(%s)\n", tc.Function.Name, tc.Function.Arguments)
}

Tool Result(工具执行结果)

// Role == schema.Tool 表示工具返回
if mv.Role == schema.Tool {
    content := drainToolResult(mv)
    fmt.Printf("[tool result] %s\n", content)
}

完整的消息序列:

[Assistant] 我来帮你看看...
[tool call] read_file({"file_path": "/path/main.go"})
[tool result] {"content": "package main..."}
[Assistant] main.go 的内容是...

后端是什么

Backend 是工具的提供者。LocalBackend 提供本地文件系统访问能力。

type Backend interface {
    // 返回可用的工具列表
    Tools(ctx context.Context) ([]*schema.ToolInfo, error)
    // 执行工具
    Invoke(ctx context.Context, toolName string, args string) (string, error)
}

除了 LocalBackend,还可以实现其他 Backend:

  • HTTPBackend:提供 HTTP 请求工具
  • DBBackend:提供数据库查询工具
  • CustomBackend:自定义业务工具

用法

cd /Users/yuanjian/go/src/cloudwego/eino-demo

# 新建会话
go run ./cmd/ch04

# 试着问:
# you> 列出当前目录的文件
# you> 读取 go.mod 文件
# you> 搜索所有包含 "func main" 的文件

这篇的局限

LocalBackend 只能访问本地文件。如果要:

  • 调用外部 API(天气、搜索)
  • 查询数据库
  • 执行远程命令

需要自己实现 Backend 或使用其他预构建的后端。