在上一篇内容里,我们已经掌握了Eino中Message和Prompt的使用方法,能搭建基础的对话流程了。但实际开发中,我们需要让AI具备调用外部工具的能力——比如查天气、查邮编、调用企业接口,而schema.ToolInfo就是实现这一能力的基础。
你可以把ToolInfo理解成写给大模型的“工具使用说明书” :模型完全依靠这份说明书,才能知道“有哪些工具可用”“每个工具能做什么”“该传什么参数”。今天这节课,我们就聚焦ToolInfo本身,手把手教大家两种最常用的ToolInfo构建方式,搭配完整可运行的案例,保证初学者也能跟着做、看得懂、跑得起。
一、先理清:ToolInfo的核心作用
在正式写代码前,先建立一个基础认知:
- • ToolInfo是静态的描述文档,只告诉模型“工具长什么样”,不包含任何执行逻辑;
- • 模型会根据ToolInfo判断是否调用工具,并生成对应的调用指令(ToolCall);
- • 一份清晰的ToolInfo,能大幅降低模型调用工具时的出错概率。
打个最通俗的比方:ToolInfo就像超市里的商品价签——价签写清了“商品名、用途、规格”,模型(顾客)看价签才知道要不要拿这个商品(工具),拿的时候要符合什么规格(参数)。
二、ToolInfo核心结构拆解
先看ToolInfo的核心结构体,我们逐字段解释,不用记源码,理解每个字段的作用即可:
| 字段名 | 核心作用 | 新手注意点 |
|---|---|---|
| Name | 工具唯一名称(比如get_city_weather) | 全局不能重复,否则模型分不清该调用哪个工具 |
| Desc | 工具功能描述 | 写得越细越好,比如“仅支持国内城市天气查询” |
| ParamsOneOf | 工具入参约束(比如“城市名是必填字符串”) | 决定模型传参是否正确 |
| Extra | 扩展字段(比如给工具打分类标签) | 不影响模型调用,可选填 |
其中,ParamsOneOf是重点,它用来描述工具的入参规则,Eino提供了两种实现方式——这也是我们本节课的核心内容。
三、两种ToolInfo构建方式
Eino支持“手动构建”和“自动推导”两种方式创建ToolInfo,我们分别讲解,最后在同一个案例里实现这两种方式。
方式1:手动构建ToolInfo
适合场景:参数少、需要灵活调整参数规则(比如动态修改必填项)、新手理解原理。
核心步骤:先定义单个参数的约束(ParameterInfo),再组装成ToolInfo。
关键知识点:ParameterInfo
单个参数的规则靠ParameterInfo描述,新手只需掌握几个核心字段:
type ParameterInfo struct {
Type DataType // 参数类型:string/int/float/bool(最常用)
Desc string // 参数说明,比如“待查询的国内城市名称”
Required bool // 是否必填:true=模型必须传这个参数
}手动构建示例(查城市邮编工具)
// 步骤1:定义参数约束(比如“城市名”是必填字符串)
zipParamMap := map[string]*schema.ParameterInfo{
"city": {
Type: schema.DataTypeString, // 参数类型:字符串
Desc: "待查询的国内城市名称,如北京、上海", // 描述要具体
Required: true, // 必须传这个参数
},
}
// 步骤2:组装成ToolInfo
zipToolInfo := &schema.ToolInfo{
Name: "get_city_zipcode", // 工具唯一名称
Desc: "查询国内城市的邮政编码,仅支持省会城市", // 功能描述
ParamsOneOf: schema.NewParamsOneOfByParams(zipParamMap), // 绑定参数约束
Extra: map[string]any{ // 扩展字段(可选)
"category": "生活工具",
},
}方式2:结构体自动推导ToolInfo
适合场景:参数多、工程化开发、想减少手写代码(避免出错)。
核心优势:通过Go结构体+标签自动生成ToolInfo,不用手动写ParameterInfo,新手也能快速上手。
关键知识点:jsonschema标签
在结构体字段上添加jsonschema标签,就能自动生成参数说明,标签格式:
jsonschema:"参数描述,required"(required可选,代表是否必填)
自动推导示例(查城市天气工具)
// 步骤1:定义工具入参结构体,添加标签
type WeatherParam struct {
City string `json:"city" jsonschema:"description=待查询的国内城市名称,如北京、上海、广州,enum=北京,上海,广州,required"`
}
// 步骤2:写工具执行函数(仅占位,本节课重点是ToolInfo,执行逻辑后续讲)
func getWeather(ctx context.Context, p *WeatherParam) (string, error) {
// 模拟返回结果(实际开发中替换为真实接口调用)
weatherMap := map[string]string{
"北京": "26℃ 晴",
"上海": "28℃ 多云",
"广州": "32℃ 雷阵雨",
}
return weatherMap[p.City], nil
}
// 步骤3:自动推导生成ToolInfo(通过toolutils.InferTool)
weatherTool, err := toolutils.InferTool(
"get_city_weather", // 工具名称
"查询国内城市当日实时气温,仅支持北京/上海/广州", // 工具描述
getWeather, // 工具执行函数
)
if err != nil {
// 错误处理(新手先打印错误即可)
fmt.Printf("自动推导ToolInfo失败:%v\n", err)
return
}
// 提取自动生成的ToolInfo
weatherToolInfo, _ := weatherTool.Info(ctx)四、完整可运行Demo:同时实现两种构建方式
接下来我们写一个完整的案例,同时实现“手动构建邮编工具”和“自动推导天气工具”,并把这两个ToolInfo绑定到模型,让模型能识别并生成对应的调用指令。
完整代码
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/schema"
)
// ==================== Eino 框架:工具定义与调用 ====================
// 本 demo 演示 Eino 的两种工具定义方式:
// 1. 手动构建 ToolInfo(邮编工具):完全手写参数约束
// 2. 自动推导 ToolInfo(天气工具):通过结构体标签自动生成
// ==================== 方式1:手动构建 ToolInfo(查邮编工具) ====================
// ZipParam 邮编工具入参(手动构建 ToolInfo 时,参数约束需单独写)。
type ZipParam struct {
City string `json:"city"`
}
// getZipcode 邮编查询执行函数(模拟数据)。
func getZipcode(ctx context.Context, p *ZipParam) (string, error) {
zipData := map[string]string{
"北京": "100000", "上海": "200000", "广州": "510000",
}
if zip, ok := zipData[p.City]; ok {
return fmt.Sprintf("%s 的邮政编码:%s", p.City, zip), nil
}
return fmt.Sprintf("暂无 %s 的邮编数据", p.City), nil
}
// buildZipTool 手动构建邮编工具的 ToolInfo + 执行函数。
// 特点:参数约束(Type/Desc/Required)完全手写,灵活但繁琐。
func buildZipTool(ctx context.Context) tool.InvokableTool {
zipTool := utils.NewTool(
&schema.ToolInfo{
Name: "get_city_zipcode",
Desc: "查询国内省会城市的邮政编码,返回6位数字字符串",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"city": {
Type: schema.String,
Desc: "待查询的国内省会城市名称,如北京、上海、广州",
// Enum: []string{"北京", "上海", "广州"},
Required: true,
},
}),
Extra: map[string]any{ // 扩展字段(可选)
"category": "生活工具",
},
}, getZipcode)
return zipTool
}
// ==================== 方式2:自动推导 ToolInfo(查天气工具) ====================
// WeatherParam 天气工具入参(带 jsonschema 标签,InferTool 据此自动生成 ToolInfo)。
type WeatherParam struct {
City string `json:"city" jsonschema:"description=待查询的国内城市名称,如北京、上海、广州,enum=北京,上海,广州,required"`
}
// getWeather 天气查询执行函数(模拟数据)。
func getWeather(ctx context.Context, p *WeatherParam) (string, error) {
weatherData := map[string]string{
"北京": "26℃ 晴,微风",
"上海": "28℃ 多云",
"广州": "32℃ 雷阵雨",
}
if w, ok := weatherData[p.City]; ok {
return fmt.Sprintf("%s 今日天气:%s", p.City, w), nil
}
return fmt.Sprintf("暂无 %s 的天气数据", p.City), nil
}
// buildWeatherTool 自动推导天气工具,返回可执行工具(含自动生成的 ToolInfo)。
// 特点:InferTool 从结构体标签自动生成参数约束,简洁但需规范标签。
func buildWeatherTool(ctx context.Context) (tool.InvokableTool, error) {
return utils.InferTool(
"get_city_weather",
"查询国内城市当日实时气温和天气状况,仅支持北京/上海/广州",
getWeather,
)
}
// printToolInfo 打印工具元信息(方便新手理解 ToolInfo 结构)。
func printToolInfo(label string, info *schema.ToolInfo) {
fmt.Printf("===== %s =====\n", label)
fmt.Printf("工具名:%s\n描述:%s\n", info.Name, info.Desc)
fmt.Printf("参数:%v\n\n", info.ParamsOneOf)
}
// ==================== 主函数:完整工具调用闭环 ====================
func main() {
ctx := context.Background()
// 1. 构建两个工具
zipTool := buildZipTool(ctx) // 方式1:手动构建 ToolInfo
weatherTool, err := buildWeatherTool(ctx) // 方式2:InferTool 自动推导
if err != nil {
fmt.Printf("构建天气工具失败:%v\n", err)
return
}
zipInfo, err := zipTool.Info(ctx)
if err != nil {
fmt.Printf("提取邮编工具信息失败:%v\n", err)
}
weatherInfo, err := weatherTool.Info(ctx)
if err != nil {
fmt.Printf("提取天气工具信息失败:%v\n", err)
return
}
// 2. 打印两种 ToolInfo(对比手动 vs 自动推导的差异)
printToolInfo("手动构建的 ToolInfo(邮编工具)", zipInfo)
printToolInfo("自动推导的 ToolInfo(天气工具)", weatherInfo)
}运行结果
执行代码后,会看到如下输出(参数里的工具ID是随机的,不影响):
===== 手动构建的 ToolInfo(邮编工具) =====
工具名:get_city_zipcode
描述:查询国内省会城市的邮政编码,返回6位数字字符串
参数:&{map[city:0xc000164690] <nil>}
===== 自动推导的 ToolInfo(天气工具) =====
工具名:get_city_weather
描述:查询国内城市当日实时气温和天气状况,仅支持北京/上海/广州
参数:&{map[] 0xc000400008}这个结果说明:
- 1. 两种方式都成功创建了ToolInfo;
- 2. 模型能识别ToolInfo,根据用户问题生成对应的工具调用指令。
五、避坑指南&使用建议
5.1 常见坑点
❌ 工具Name重复:比如两个工具都叫get_info,模型会分不清该调用哪个;
❌ 参数Desc太简单:比如只写“城市名”,模型可能传“北京市海淀区”这种不符合要求的参数;
❌ 手动构建时参数类型写错:比如把“城市名”写成DataTypeInt,模型会生成数字参数,直接出错;
❌ 自动推导时漏加jsonschema标签:参数说明会缺失,模型调用准确率下降。
5.2 使用建议
- 1. 学习阶段:先用手动构建方式,理解ToolInfo的原理;
- 2. 实际开发:优先用自动推导方式(减少手写代码,降低出错概率);
- 3. 描述编写:遵循“功能+约束”原则,比如“查询国内省会城市邮编,仅支持北京/上海”;
- 4. 参数设计:尽量简单,优先用string/int等基础类型,新手别用复杂的数组/对象类型。
六、知识点回顾
- 1. ToolInfo是给模型看的“工具说明书”,核心字段是Name、Desc、ParamsOneOf;
- 2. 手动构建ToolInfo:适合理解原理、参数少的场景,需手写ParameterInfo;
- 3. 自动推导ToolInfo:适合工程化开发,通过结构体+jsonschema标签自动生成,推荐使用;
- 4. 模型会根据ToolInfo生成ToolCall指令,Name是匹配工具的关键。
MiaoAll