TL;DR: Tool Calling 让 LLM 能够调用外部函数。Eino 提供
utils.InferTool从 Go 函数自动推断工具定义,通过chatModel.BindTools()绑定工具,模型返回的response.ToolCalls包含要调用的工具和参数。
Tool Calling 流程
用户问题 → LLM 分析 → 返回 ToolCalls → 执行工具 → 结果返回 LLM → 最终回答
关键点:
- LLM 不执行工具,只决定调用哪个工具、传什么参数
- 你的代码负责执行工具并把结果返回给 LLM
- LLM 基于工具结果生成最终回答
定义工具
方式一:InferTool(推荐)
从 Go 函数自动推断工具的 JSON Schema:
type WeatherInput struct {
City string `json:"city" jsonschema:"description=城市名称,如:北京、上海"`
}
type WeatherOutput struct {
City string `json:"city"`
Temperature int `json:"temperature"`
Condition string `json:"condition"`
}
weatherTool, _ := utils.InferTool(
"get_weather", // 工具名称
"获取指定城市的当前天气信息", // 工具描述
func(ctx context.Context, input *WeatherInput) (*WeatherOutput, error) {
// 实现逻辑
return &WeatherOutput{City: input.City, Temperature: 25, Condition: "晴"}, nil
},
)
jsonschema:"description=..." 标签会被提取为参数描述,帮助 LLM 理解如何使用工具。
方式二:实现 Tool 接口
手动定义工具的 Schema:
type AddTool struct{}
func (t *AddTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "add",
Desc: "计算两个数的和",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"a": {Desc: "第一个数", Type: schema.Number, Required: true},
"b": {Desc: "第二个数", Type: schema.Number, Required: true},
}),
}, nil
}
func (t *AddTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
var input struct {
A float64 `json:"a"`
B float64 `json:"b"`
}
json.Unmarshal([]byte(argumentsInJSON), &input)
return fmt.Sprintf(`{"result": %v}`, input.A+input.B), nil
}
完整示例:多工具调用
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/schema"
"github.com/joho/godotenv"
)
func init() {
godotenv.Load()
}
type WeatherInput struct {
City string `json:"city" jsonschema:"description=城市名称"`
}
type AddInput struct {
A float64 `json:"a" jsonschema:"description=第一个数"`
B float64 `json:"b" jsonschema:"description=第二个数"`
}
func main() {
ctx := context.Background()
// 1. 定义工具
weatherTool, _ := utils.InferTool("get_weather", "获取城市天气",
func(ctx context.Context, input *WeatherInput) (string, error) {
data := map[string]string{"北京": "晴,25°C", "上海": "多云,28°C"}
if w, ok := data[input.City]; ok {
return w, nil
}
return "暂无数据", nil
})
addTool, _ := utils.InferTool("add", "计算两个数的和",
func(ctx context.Context, input *AddInput) (string, error) {
return fmt.Sprintf(`{"result": %v}`, input.A+input.B), nil
})
// 2. 创建工具映射表(用于路由)
toolMap := map[string]tool.InvokableTool{
"get_weather": weatherTool,
"add": addTool,
}
// 3. 收集工具信息
var toolInfos []*schema.ToolInfo
for _, t := range toolMap {
info, _ := t.Info(ctx)
toolInfos = append(toolInfos, info)
}
// 4. 创建模型并绑定工具
chatModel, _ := ark.NewChatModel(ctx, &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL_ID"),
})
chatModel.BindTools(toolInfos)
// 5. 发送请求
messages := []*schema.Message{
schema.UserMessage("北京天气怎么样?另外算一下 123+456"),
}
response, _ := chatModel.Generate(ctx, messages)
// 6. 处理工具调用
if len(response.ToolCalls) > 0 {
messages = append(messages, response)
for _, tc := range response.ToolCalls {
fmt.Printf("🔧 调用: %s(%s)\n", tc.Function.Name, tc.Function.Arguments)
t, ok := toolMap[tc.Function.Name]
if !ok {
log.Printf("未知工具: %s", tc.Function.Name)
continue
}
result, _ := t.InvokableRun(ctx, tc.Function.Arguments)
fmt.Printf(" 结果: %s\n", result)
messages = append(messages, &schema.Message{
Role: schema.Tool,
Content: result,
ToolCallID: tc.ID,
})
}
// 7. 基于工具结果生成最终回答
finalResponse, _ := chatModel.Generate(ctx, messages)
fmt.Println("\n🤖 最终回答:", finalResponse.Content)
}
}
输出示例:
🔧 调用: get_weather({"city":"北京"})
结果: 晴,25°C
🔧 调用: add({"a":123,"b":456})
结果: {"result": 579}
🤖 最终回答: 北京今天天气晴朗,气温25°C。123+456的结果是579。
消息序列
Tool Calling 的消息流:
1. User: "北京天气?123+456=?"
2. Assistant: { ToolCalls: [{name:"get_weather"}, {name:"add"}] } ← 模型决定调用
3. Tool: "晴,25°C" ← get_weather 结果
4. Tool: {"result": 579} ← add 结果
5. Assistant: "北京晴朗25°C,123+456=579" ← 最终回答
关键:ToolCallID 必须匹配,模型才能知道哪个结果对应哪个调用。
Trade-offs
| 方式 | 优点 | 缺点 |
|---|---|---|
InferTool | 代码少,自动推断 Schema | 依赖 struct tag,灵活性低 |
| 实现接口 | 完全控制 Schema | 代码多,需手动定义参数 |
工具设计建议:
- 工具描述要清晰,帮助 LLM 判断何时使用
- 参数描述要具体,包含示例值
- 返回 JSON 格式,便于 LLM 解析
总结
| 概念 | 说明 |
|---|---|
utils.InferTool | 从 Go 函数自动推断工具定义 |
chatModel.BindTools() | 将工具绑定到模型 |
response.ToolCalls | 模型返回的工具调用列表 |
InvokableRun() | 执行工具,输入输出都是 JSON 字符串 |
schema.Tool | 工具结果消息的角色 |
ToolCallID | 关联工具调用和结果 |