第十二篇:Eino ToolInfo工具-给模型看的说明书

2026-04-04 52 0

在上一篇内容里,我们已经掌握了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. 1. 两种方式都成功创建了ToolInfo;
  2. 2. 模型能识别ToolInfo,根据用户问题生成对应的工具调用指令。

五、避坑指南&使用建议

5.1 常见坑点

❌ 工具Name重复:比如两个工具都叫get_info,模型会分不清该调用哪个;
❌ 参数Desc太简单:比如只写“城市名”,模型可能传“北京市海淀区”这种不符合要求的参数;
❌ 手动构建时参数类型写错:比如把“城市名”写成DataTypeInt,模型会生成数字参数,直接出错;
❌ 自动推导时漏加jsonschema标签:参数说明会缺失,模型调用准确率下降。

5.2 使用建议

  1. 1. 学习阶段:先用手动构建方式,理解ToolInfo的原理;
  2. 2. 实际开发:优先用自动推导方式(减少手写代码,降低出错概率);
  3. 3. 描述编写:遵循“功能+约束”原则,比如“查询国内省会城市邮编,仅支持北京/上海”;
  4. 4. 参数设计:尽量简单,优先用string/int等基础类型,新手别用复杂的数组/对象类型。

六、知识点回顾

  1. 1. ToolInfo是给模型看的“工具说明书”,核心字段是Name、Desc、ParamsOneOf;
  2. 2. 手动构建ToolInfo:适合理解原理、参数少的场景,需手写ParameterInfo;
  3. 3. 自动推导ToolInfo:适合工程化开发,通过结构体+jsonschema标签自动生成,推荐使用;
  4. 4. 模型会根据ToolInfo生成ToolCall指令,Name是匹配工具的关键。

相关文章

第十六篇:Eino入门——核心组件总览与场景选型总结
第十五篇:Eino ChatModelAgent 模型自己决定是否调用工具
第十四篇:Eino ToolsNode 工具调用执行器
第十三篇:InvokableTool-把Go函数包装成模型可调用的工具
第十一篇:Eino Message与Prompt上下文编排
第十篇:Eino ChatModel:将大模型调用抽象成可复用组件

发布评论