Go + Eino 构建 AI Agent(二):Tool Calling

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:

go 复制代码
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:

go 复制代码
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
}

完整示例:多工具调用

go 复制代码
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)
	}
}

输出示例:

css 复制代码
🔧 调用: get_weather({"city":"北京"})
   结果: 晴,25°C
🔧 调用: add({"a":123,"b":456})
   结果: {"result": 579}

🤖 最终回答: 北京今天天气晴朗,气温25°C。123+456的结果是579。

消息序列

Tool Calling 的消息流:

css 复制代码
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 关联工具调用和结果
相关推荐
侧岭灵风1 小时前
人工智能各名词解释
人工智能·机器学习
sevenez2 小时前
2026年AI驱动下人力资源数字化未来趋势分析
人工智能
KvPiter2 小时前
《solopreneur》跑一次完整的 AI 工程任务
人工智能
Maynor9962 小时前
Openclaw第3章:进阶部署
人工智能
KG_LLM图谱增强大模型2 小时前
TKG-Thinker:通过智能体强化学习实现时序知识图谱的动态推理
人工智能·知识图谱
沛沛老爹2 小时前
AI Agent技能有效性评估:构建可量化的KPI度量体系
人工智能·度量·smart原则·评估目标·评估维度·核心指标
cxr8282 小时前
OpenClaw:为智能体赋予操作系统级的生命力
人工智能·ai智能体·openclaw
lisw052 小时前
BTSP在人工智能中的应用前景:从神经机制到智能范式革新!
人工智能·神经生物学
AI科技星2 小时前
时空的几何本源与物理现象的建构:论统一场论的宇宙二元论与观察者中心范式
人工智能·线性代数·算法·矩阵·数据挖掘