接口定义:
// BaseTool get tool info for ChatModel intent recognition.
type BaseTool interface {
Info(ctx context.Context) (*schema.ToolInfo, error)
}
// InvokableTool the tool for ChatModel intent recognition and ToolsNode execution.
// nolint: byted_s_interface_name
type InvokableTool interface {
BaseTool
// InvokableRun call function with arguments in JSON format
InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
}
Info方法: 返回工具的信息, 如Name, Desc及ParamsOneOf , 其中ParamsOneOf 字段是用于描述工具参数的结构体字段,它定义了工具调用时所需的参数规范,供大模型理解和使用, 这里需要遵从openApI 3规范, 详情参见规范中的schema定义(https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object) 。
InvokableRun方法: 方法调用, 参数为json字符串
接口实现:
type invokableTool[T, D any] struct {
info *schema.ToolInfo
um UnmarshalArguments
m MarshalOutput
Fn OptionableInvokeFunc[T, D]
}
func (i *invokableTool[T, D]) Info(ctx context.Context) (*schema.ToolInfo, error) {
return i.info, nil
}
// InvokableRun invokes the tool with the given arguments.
func (i *invokableTool[T, D]) InvokableRun(ctx context.Context, arguments string, opts ...tool.Option) (output string, err error) {
var inst T
if i.um != nil {
var val interface{}
val, err = i.um(ctx, arguments)
if err != nil {
return "", fmt.Errorf("[LocalFunc] failed to unmarshal arguments, toolName=%s, err=%w", i.getToolName(), err)
}
gt, ok := val.(T)
if !ok {
return "", fmt.Errorf("[LocalFunc] invalid type, toolName=%s, expected=%T, given=%T", i.getToolName(), inst, val)
}
inst = gt
} else {
inst = generic.NewInstance[T]()
err = sonic.UnmarshalString(arguments, &inst)
if err != nil {
return "", fmt.Errorf("[LocalFunc] failed to unmarshal arguments in json, toolName=%s, err=%w", i.getToolName(), err)
}
}
resp, err := i.Fn(ctx, inst, opts...)
if err != nil {
return "", fmt.Errorf("[LocalFunc] failed to invoke tool, toolName=%s, err=%w", i.getToolName(), err)
}
if i.m != nil {
output, err = i.m(ctx, resp)
if err != nil {
return "", fmt.Errorf("[LocalFunc] failed to marshal output, toolName=%s, err=%w", i.getToolName(), err)
}
} else {
output, err = sonic.MarshalString(resp)
if err != nil {
return "", fmt.Errorf("[LocalFunc] failed to marshal output in json, toolName=%s, err=%w", i.getToolName(), err)
}
}
return output, nil
}
invokableTool 类型包含四个属性:
- info , ToolInfo类型, 工具的描述信息
- um UnmarshalArguments, 自定义反系列化函数, 用于将json字符串反序列化为golang的结构体, 对应调用函数Fn 的input
type UnmarshalArguments func(ctx context.Context, arguments string) (interface{}, error)
- m MarshalOutput 自定义序列化函数, 用于将工具调用输出的golang结构体(对应调用函数Fn 的output)序列化化为json字符串
type MarshalOutput func(ctx context.Context, output interface{}) (string, error)
- Fn OptionableInvokeFunc[T, D]
Eino 中的工具调用
要不要调工具、调哪一个工具”完全由大模型自己决定,Eino 只是把「有哪些工具、每个工具能干什么」提前告诉模型,然后把模型的调用意图解析出来并执行。

在 Eino 中,工具最终会被包装进 ToolsNode,并通过编排(Chain/Graph)与模型串联, 那么模型是如何调用工具, 并给工具传参的?
一、模型侧:模型如何“知道”要调用哪个工具?
Eino 的 ChatModel 实现了 WithTools 方法,用于将工具信息注册到模型上下文:
chatModel = chatModel.WithTools([]*schema.ToolInfo{
{
Name: "get_weather",
Desc: "获取城市天气",
ParamsOneOf: schema.NewParamsOneOfByParams(...),
},
})
作用:告诉模型有哪些工具可用,每个工具的名称、描述、参数结构。
机制:模型在生成回复时,会自动推理判断是否需要调用工具,如果需要会返回一个 ToolCall 结构,包含:
- name:工具名
- arguments:JSON 字符串形式的参数
二、框架侧: 触发工具调用, 并返回结果给大模型
在 Eino 中,工具最终会被包装进 ToolsNode, ToolsNode 会根据上游ChatModel节点返回的 msg.ToolCalls 提取工具名和参数, 并通过 genToolCallTasks函数,将工具名映射到实际的 InvokableTool 实现, 最后调用对应工具的 InvokableRun(ctx, argumentsInJSON) 方法, 将结果返回给大模型。
type toolsTuple struct {
indexes map[string]int // 工具名称和ID的映射
}
type Message struct {
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
func (tn *ToolsNode) genToolCallTasks(ctx context.Context, tuple *toolsTuple, input *schema.Message, executedTools map[string]string, isStream bool) ([]toolCallTask, error) {
index, ok := tuple.indexes[toolCall.Function.Name]
if !ok {
if tn.unknownToolHandler == nil {
return nil, fmt.Errorf("tool %s not found in toolsNode indexes", toolCall.Function.Name)
}
toolCallTasks[i] = newUnknownToolTask(toolCall.Function.Name, toolCall.Function.Arguments, toolCall.ID, tn.unknownToolHandler)
}
}
tuple : 已注册的工具三元组,
input : 上游模型的输入Message信息,ToolCalls 属性描述模型需要调用工具信息, genToolCallTasks通过Function Name 在已注册的Tools中找到需要调用的工具。
上述流程的伪代码实现:
// 1. 把工具塞进模型
model := openai.NewChatModel(...).
WithTools(toolInfos)
// 2. 模型返回
msg, _ := model.Generate(ctx, messages) //messages为用户输入
// 3. Eino 判断
if len(msg.ToolCalls) > 0 {
results := toolsNode.Invoke(ctx, msg.ToolCalls)
msg.Content= msg.Content + results // observation
goto 2 // 继续对话循环
}
实现接口的方式:
- 方式一: 直接实现接口, 定义tool类型, 实现InvokableTool 接口中的Info 和InvokableRun 方法:
type AddUser struct{}
func (t *AddUser) Info(_ context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "add_user",
Desc: "add user",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
// omit,参考上文中构建 params 约束的方式
}),
}, nil
}
func (t *AddUser) InvokableRun(_ context.Context, argumentsInJSON string, _ ...tool.Option) (string, error) {
// 1. 反序列化 argumentsInJSON,处理 option 等
user, _ := json.Unmarshal([]byte(argumentsInJSON))
// 2. 处理业务逻辑
// 3. 把结果序列化为 string 并返回
return `{"msg": "ok"}`, nil
}
- 方式二:把本地函数转为 tool,Eino 中提供了 NewTool 的方法来把一个函数转成 tool,同时,针对为参数约束通过结构体的 tag 来表示的场景提供了 InferTool 的方法,让构建的过程更加简单。
当一个函数满足下面这种函数签名时,就可以用 NewTool 把其变成一个 InvokableTool :
type InvokeFunc[T, D any] func(ctx context.Context, input T) (output D, err error)
NewTool 的方法如下:
// 代码见: github.com/cloudwego/eino/components/tool/utils/invokable_func.go
func NewTool[T, D any](desc *schema.ToolInfo, i InvokeFunc[T, D], opts ...Option) tool.InvokableTool
从 NewTool 中可以看出,构建一个 tool 的过程需要分别传入 ToolInfo 和 InvokeFunc ,其中,ToolInfo 中包含 ParamsOneOf 的部分,这代表着函数的入参约束, 需要手工构建并维护, 且需要和InvokeFunc 的 input 参数需要保持一致。更优雅的解决方法是 “参数约束直接维护在 input 参数类型定义中”, 同过GoStruct2ParamsOneOf 方法,自动从类型属性的Tag标签推导出约束信息:
package main
import (
"fmt"
"github.com/cloudwego/eino/components/tool/utils"
)
func main() {
type PostRequest struct {
URL string `json:"url" jsonschema:"description=The URL to make the POST request"`
Body string `json:"body" jsonschema:"description=The body to send in the POST request"`
}
param, err := utils.GoStruct2ParamsOneOf[PostRequest]()
if err != nil {
panic(err)
}
fmt.Println(param)
}
http post 请求tool 实现:
- 定义PostRequestTool类型:
type PostRequestTool struct {
config *Config // 工具的name , dese, 及自定义headers信息
client *http.Client
}
- Post 请求方法实现:
type PostRequest struct {
URL string `json:"url" jsonschema:"description=The URL to make the POST request"`
Body string `json:"body" jsonschema:"description=The body to send in the POST request"`
}
func (r *PostRequestTool) Post(ctx context.Context, req *PostRequest) (string, error) {
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, req.URL, strings.NewReader(req.Body))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
for key, value := range r.config.Headers {
httpReq.Header.Set(key, value)
}
resp, err := r.client.Do(httpReq)
if err != nil {
return "", fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %w", err)
}
return string(body), nil
}
将PostRequestTool 的Post 函数转为 tool :
func NewTool(ctx context.Context, config *Config) (tool.InvokableTool, error) {
reqTool, err := newRequestTool(config) // 通过配置实例化一个PostRequestTool 对象
if err != nil {
return nil, fmt.Errorf("failed to create request tool: %w", err)
}
invokableTool, err := utils.InferTool(config.ToolName, config.ToolDesc, reqTool.Post)
if err != nil {
return nil, fmt.Errorf("failed to infer the tool: %w", err)
}
return invokableTool, nil
}
http post 请求工具使用完整示例:
package main
import (
"context"
"fmt"
"log"
"github.com/bytedance/sonic"
post "github.com/cloudwego/eino-ext/components/tool/httprequest/post"
)
func main() {
config := &post.Config{
Headers: map[string]string{
"User-Agent": "MyCustomAgent",
"Content-Type": "application/json; charset=UTF-8",
},
}
ctx := context.Background()
tool, err := post.NewTool(ctx, config)
if err != nil {
log.Fatalf("Failed to create tool: %v", err)
}
request := &post.PostRequest{
URL: "https://jsonplaceholder.typicode.com/posts",
Body: `{"title": "my title","body": "my body","userId": 1}`,
}
jsonReq, err := sonic.Marshal(request)
if err != nil {
log.Fatalf("Error marshaling JSON: %v", err)
}
resp, err := tool.InvokableRun(ctx, string(jsonReq))
if err != nil {
log.Fatalf("Post failed: %v", err)
}
fmt.Println(resp)
}