Eino: ToolsNode&Tool 使用说明

基本介绍

Tool 在 eino 框架中的定义是“ChatModel 能够选择调用的外部能力”,包括本地函数,MCP server tool 等。

ToolsNode 是 eino 框架指定的”Tool 执行器“,无论是 Graph 内还是 Agent 中,Tool 的执行都要通过 ToolsNode:

// compose/tool_node.go

// run tools using `Invoke`
func (tn *ToolsNode) Invoke(ctx context.Context, input *schema.Message,
    opts ...ToolsNodeOption) ([]*schema.Message, error)
    
// run tools using `Stream`
func (tn *ToolsNode) Stream(ctx context.Context, input *schema.Message,
    opts ...ToolsNodeOption) (*schema.StreamReader[[]*schema.Message], error)

给 ToolsNode 配置一个 Tool 列表以及一些配套策略:

// compose/tool_node.go

type ToolsNodeConfig struct {
    Tools []tool.BaseTool

    UnknownToolsHandler func(ctx context.Context, name, input string) (string, error)

    ExecuteSequentially bool

    ToolArgumentsHandler func(ctx context.Context, name, arguments string) (string, error)

    ToolCallMiddlewares []ToolMiddleware
}

这样 ToolsNode 就“能够执行配置的 Tool”,并获得一些扩展能力,如执行时序、异常处理、入参处理、middleware 扩展等。

ToolsNode 如何“决策”应该执行哪个 Tool?它不决策,而是依据输入的 *schema.Message 来执行:

// schema/message.go

type Message struct {
    // role should be 'assistant' for tool call message
    Role RoleType `json:"role"`

    // here each `ToolCall` is generated by ChatModel and to be executed by ToolsNode
    ToolCalls []ToolCall `json:"tool_calls,omitempty"`
    
    // other fields...
}

// ToolCall is the tool call in a message.
// It's used in Assistant Message when there are tool calls should be made.
type ToolCall struct {
    // Index is used when there are multiple tool calls in a message.
    // In stream mode, it's used to identify the chunk of the tool call for merging.
    Index *int `json:"index,omitempty"`
    // ID is the id of the tool call, it can be used to identify the specific tool call.
    ID string `json:"id"`
    // Type is the type of the tool call, default is "function".
    Type string `json:"type"`
    // Function is the function call to be made.
    Function FunctionCall `json:"function"`

    // Extra is used to store extra information for the tool call.
    Extra map[string]any `json:"extra,omitempty"`
}

// FunctionCall is the function call in a message.
// It's used in Assistant Message.
type FunctionCall struct {
    // Name is the name of the function to call, it can be used to identify the specific function.
    Name string `json:"name,omitempty"`
    // Arguments is the arguments to call the function with, in JSON format.
    Arguments string `json:"arguments,omitempty"`
}

ChatModel(LLM) 生成要调用的 []ToolCall(包含 ToolName,Argument 等),放到 *schema.Message 中传给 ToolsNode。ToolsNode 针对每个 ToolCall 实际执行一次调用。

如果配置了 ExecuteSequentially,则 ToolsNode 会按照 []ToolCall 中的先后顺序来执行工具。

每个 ToolCall 调用完成后的结果,又会封装为 *schema.Message,作为 ToolsNode 输出的一部分。

**Tool **定义

接口定义

Tool 组件提供了三个层次的接口:

代码位置:eino/compose/tool/interface.go

// 基础工具接口,提供工具信息
type BaseTool interface {
    Info(ctx context.Context) (*schema.ToolInfo, error)
}

// 可调用的工具接口,支持同步调用
type InvokableTool interface {
    BaseTool
    InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
}

// 支持流式输出的工具接口
type StreamableTool interface {
    BaseTool
    StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
}

Info 方法

  • 功能:获取工具的描述信息
  • 参数:
    • ctx:上下文对象
  • 返回值:
    • *schema.ToolInfo:工具的描述信息
    • error:获取信息过程中的错误

InvokableRun 方法

  • 功能:同步执行工具
  • 参数:
    • ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager
    • argumentsInJSON:JSON 格式的参数字符串
    • opts:工具执行的选项
  • 返回值:
    • string:执行结果
    • error:执行过程中的错误

StreamableRun 方法

  • 功能:以流式方式执行工具
  • 参数:
    • ctx:上下文对象,用于传递请求级别的信息,同时也用于传递 Callback Manager
    • argumentsInJSON:JSON 格式的参数字符串
    • opts:工具执行的选项
  • 返回值:
    • *schema.StreamReader[string]:流式执行结果
    • error:执行过程中的错误

ToolInfo 结构体

代码位置:eino/schema/tool.go

type ToolInfo struct {
    // 工具的唯一名称,用于清晰地表达其用途
    Name string
    // 用于告诉模型如何/何时/为什么使用这个工具
    // 可以在描述中包含少量示例
    Desc string
    // 工具接受的参数定义
    // 可以通过两种方式描述:
    // 1. 使用 ParameterInfo:schema.NewParamsOneOfByParams(params)
    // 2. 使用 OpenAPIV3:schema.NewParamsOneOfByOpenAPIV3(openAPIV3)
    *ParamsOneOf
}

公共 Option

Tool 组件使用 ToolOption 来定义可选参数, ToolsNode 没有抽象公共的 option。每个具体的实现可以定义自己的特定 Option,通过 WrapToolImplSpecificOptFn 函数包装成统一的 ToolOption 类型。

使用方式

import (
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/schema"
)

// 创建工具节点
toolsNode := compose.NewToolsNode([]tool.Tool{
    searchTool,    // 搜索工具
    weatherTool,   // 天气查询工具
    calculatorTool, // 计算器工具
})

// Mock LLM 输出作为输入
input := &schema.Message{
    Role: schema.Assistant,
    ToolCalls: []schema.ToolCall{
       {
          Function: schema.FunctionCall{
             Name:      "weather",
             Arguments: `{"city": "深圳", "date": "tomorrow"}`,
          },
       },
    },
}

toolMessages, err := toolsNode.Invoke(ctx, input)

ToolsNode 通常不会被单独使用,一般用于编排之中接在 ChatModel 之后。

在编排中使用

import (
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/schema"
)

// 创建工具节点
toolsNode := compose.NewToolsNode([]tool.Tool{
    searchTool,    // 搜索工具
    weatherTool,   // 天气查询工具
    calculatorTool, // 计算器工具
})

// 在 Chain 中使用
chain := compose.NewChain[*schema.Message, []*schema.Message]()
chain.AppendToolsNode(toolsNode)

// graph 中
graph := compose.NewGraph[*schema.Message, []*schema.Message]()
graph.AddToolsNode(toolsNode)

Option 机制

自定义 Tool 可根据自己需要实现特定的 Option:

import "github.com/cloudwego/eino/components/tool"

// 定义 Option 结构体
type MyToolOptions struct {
    Timeout time.Duration
    MaxRetries int
    RetryInterval time.Duration
}

// 定义 Option 函数
func WithTimeout(timeout time.Duration) tool.Option {
    return tool.WrapImplSpecificOptFn(func(o *MyToolOptions) {
        o.Timeout = timeout
    })
}

Option 和 Callback 使用

Callback 使用示例

import (
    "context"

    callbackHelper "github.com/cloudwego/eino/utils/callbacks"
    "github.com/cloudwego/eino/callbacks"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/components/tool"
)

// 创建 callback handler
handler := &callbackHelper.ToolCallbackHandler{
    OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *tool.CallbackInput) context.Context {
       fmt.Printf("开始执行工具,参数: %s\n", input.ArgumentsInJSON)
       return ctx
    },
    OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *tool.CallbackOutput) context.Context {
       fmt.Printf("工具执行完成,结果: %s\n", output.Response)
       return ctx
    },
    OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*tool.CallbackOutput]) context.Context {
       fmt.Println("工具开始流式输出")
       go func() {
          defer output.Close()

          for {
             chunk, err := output.Recv()
             if errors.Is(err, io.EOF) {
                return
             }
             if err != nil {
                return
             }
             fmt.Printf("收到流式输出: %s\n", chunk.Response)
          }
       }()
       return ctx
    },
}

// 使用 callback handler
helper := callbackHelper.NewHandlerHelper().
    Tool(handler).
    Handler()
 
/*** compose a chain
* chain := NewChain
* chain.appendxxx().
*       appendxxx().
*       ...
*/

// 在运行时使用
runnable, err := chain.Compile()
if err != nil {
    return err
}
result, err := runnable.Invoke(ctx, input, compose.WithCallbacks(helper))

如何获取 ToolCallID

在 tool 函数体、tool callback handler 中,都可以通过 compose.GetToolCallID(ctx) 函数获取当前 Tool 的 ToolCallID。

已有实现

  1. Google Search Tool: 基于 Google 搜索的工具实现 Tool - Googlesearch
  2. duckduckgo search tool: 基于 duckduckgo 搜索的工具实现 Tool - DuckDuckGoSearch
  3. MCP: 把 mcp server 作为 toolTool - MCP

v0.5.x->0.6.x

鉴于以下两点考虑:

  1. 各大模型厂商 API、MCP Tool 协议约定使用 JSONSchema 来描述工具 input/output schema。
  2. Eino 引用的 getkin/kin-openapi@v0.118.0 有安全问题,且 kin-openapi 安全版本有不兼容更新。

Eino 移除了 OpenAPI schema 3.0 相关的所有定义与方法,转为使用 JSONSchema 2020-12。具体移除及增加的定义与方法详见 https://github.com/cloudwego/eino/discussions/397

升级后,部分 eino-ext module 可能报错“undefined: schema.NewParamsOneOfByOpenAPIV3”等问题,升级报错的 eino-ext module 到最新版本即可。

如果 schema 改造比较复杂,可以使用 https://github.com/cloudwego/eino/discussions/397 中提供的工具方法辅助转换。


最后修改 December 12, 2025 : chore: update websocket docs (#1479) (967538e)