让 LLM 从“只会聊天”变成“会干活”——通过调用外部工具扩展能力边界


一、Tool Calling 是什么?

Tool Calling(工具调用) 是一种让大语言模型(LLM)能够调用外部函数或 API 的技术模式。简单说,就是让 AI 不仅能“动脑”(推理、决策),还能“动手”去查数据库、做计算、调接口,然后把真实结果告诉你。

为什么需要它?

LLM的短板

例子

后果

知识时效性差(无法获取实时信息)

“帮我查5月30日北京到上海最便宜的机票” 

LLM训练数据有截止日期,无法获取实时航班价格,只能凭过时数据猜测,导致信息不准确。

事实性错误/幻觉严重(不会精确计算)

“算出含税总价” 

LLM本质是概率生成,对税费计算这类精确任务容易出错,产生幻觉,给出错误的总价。

无环境交互能力(无法访问内部数据/系统)

“我的订单出票了吗?”

LLM只能输出文本,无法查询数据库或调用API,无法获取真实的订单状态。

Tool Calling(工具调用) 就是解决这三大短板的钥匙。


二、业务场景:机票查询 Agent

User Input(用户输入):

“帮我查一下 5 月 30 日北京到上海最便宜的机票,并算出含税总价”

问题拆解(Task Decomposition)

这个需求不是一句 SQL 或接口能搞定的,它本质上是多步推理 + 多次工具调用:

  1. 查航班列表 → search_flights
  2. 找最便宜航班 → LLM 推理
  3. 查税费规则 → get_tax_rate
  4. 计算含税总价 → calc_total_price
  5. 返回结构化结果

👉 这就是典型 Tool Calling + ReAct 场景


三、定义工具(Tool Registry)

后端需要预先定义可供 LLM 调用的工具,告诉 LLM“你有这些工具可以用”。

// ===== Define Tool Interface(定义工具接口)=====

// ToolInterface(工具接口):所有工具必须实现这些方法
type ToolInterface interface {
    Name() string                          // Tool name(工具名称)
    Description() string                   // Tool description(工具描述)
    Parameters() map[string]ParameterSpec  // Input parameters(输入参数定义)
    Execute(args map[string]interface{}) (interface{}, error)  // Execute logic(执行逻辑)
}

// ParameterSpec(参数规格):定义每个参数的类型和约束
type ParameterSpec struct {
    Type        string `json:"type"`        // Parameter type (string, number)
    Description string `json:"description"` // Parameter description
    Required    bool   `json:"required"`    // Is it required?
}

1️⃣ 航班查询工具(search_flights)

type SearchFlightsTool struct{}

func (t *SearchFlightsTool) Name() string {
    return "search_flights"  // Tool name used by LLM
}

func (t *SearchFlightsTool) Description() string {
    return "Search for available flights by departure, destination, and date"
}

func (t *SearchFlightsTool) Parameters() map[string]ParameterSpec {
    return map[string]ParameterSpec{
        "from": {Type: "string", Description: "Departure city(出发城市)", Required: true},
        "to":   {Type: "string", Description: "Destination city(目的地城市)", Required: true},
        "date": {Type: "string", Description: "Travel date in YYYY-MM-DD format", Required: true},
    }
}

func (t *SearchFlightsTool) Execute(args map[string]interface{}) (interface{}, error) {
    // Extract parameters(提取参数)
    from, _ := args["from"].(string)
    to, _ := args["to"].(string)
    date, _ := args["date"].(string)

    // Mock data(模拟数据)—— 真实项目这里会调数据库或 API
    flights := []map[string]interface{}{
        {"flight": "CA123", "price": 800},
        {"flight": "MU456", "price": 650},
        {"flight": "FM789", "price": 720},
    }
    return flights, nil
}

2️⃣ 税费查询工具(get_tax_rate)

真实机票的“含税总价”不是简单的“票价 × 税率”,而是:含税总价 = 票价 + 机建费 + 燃油附加费

type GetTaxRateTool struct{}

func (t *GetTaxRateTool) Name() string {
    return "get_tax_rate"
}

func (t *GetTaxRateTool) Description() string {
    return "Get the tax rate (airport fee + fuel fee) for a specific route"
}

func (t *GetTaxRateTool) Parameters() map[string]ParameterSpec {
    return map[string]ParameterSpec{
        "from": {Type: "string", Description: "Departure city(出发城市)", Required: true},
        "to":   {Type: "string", Description: "Destination city(目的地城市)", Required: true},
    }
}

func (t *GetTaxRateTool) Execute(args map[string]interface{}) (interface{}, error) {
    // 国内航线固定:机建费50元,燃油费按距离(这里简化固定140元)
    return map[string]float64{
        "airport_fee": 50.0,  // 机场建设费
        "fuel_fee":    140.0, // 燃油附加费
    }, nil
}

3️⃣ 价格计算工具(calc_total_price)

type CalcTotalPriceTool struct{}

func (t *CalcTotalPriceTool) Name() string {
    return "calc_total_price"
}

func (t *CalcTotalPriceTool) Description() string {
    return "Calculate total price including tax: base_price + airport_fee + fuel_fee"
}

func (t *CalcTotalPriceTool) Parameters() map[string]ParameterSpec {
    return map[string]ParameterSpec{
        "base_price":  {Type: "number", Description: "Base ticket price(基础票价)", Required: true},
        "airport_fee": {Type: "number", Description: "Airport construction fee(机场建设费)", Required: true},
        "fuel_fee":    {Type: "number", Description: "Fuel surcharge(燃油附加费)", Required: true},
    }
}

func (t *CalcTotalPriceTool) Execute(args map[string]interface{}) (interface{}, error) {
    basePrice, _ := args["base_price"].(float64)
    airportFee, _ := args["airport_fee"].(float64)
    fuelFee, _ := args["fuel_fee"].(float64)
    
    total := basePrice + airportFee + fuelFee  // Formula(计算公式)
    return total, nil
}

四、Tool Registry(工具注册中心)

注册中心是用来管理所有工具的“菜单”,告诉 LLM 有哪些工具可用。

// ===== Tool Registry(工具注册中心)=====

type ToolRegistry struct {
    tools map[string]ToolInterface  // Store tools by name
    mu    sync.RWMutex              // Read-write lock(读写锁)
}

func NewRegistry() *ToolRegistry {
    return &ToolRegistry{
        tools: make(map[string]ToolInterface),
    }
}

func (r *ToolRegistry) Register(tool ToolInterface) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    name := tool.Name()
    if _, exists := r.tools[name]; exists {
        return fmt.Errorf("tool %s already registered(工具已存在)", name)
    }
    r.tools[name] = tool
    return nil
}

func (r *ToolRegistry) GetTool(name string) (ToolInterface, bool) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    tool, exists := r.tools[name]
    return tool, exists
}

五、核心循环:LLM 调用 + 工具执行

这是最核心的部分——LLM 决定调哪个工具,Go 后端负责执行,然后把结果返回给 LLM 继续推理。

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "os"

    "github.com/sashabaranov/go-openai"  // OpenAI client(兼容DeepSeek、Ollama)
)

// ===== Tool Executor(工具执行器)=====

type ToolExecutor struct {
    registry *ToolRegistry
}

func NewToolExecutor(registry *ToolRegistry) *ToolExecutor {
    return &ToolExecutor{registry: registry}
}

func (e *ToolExecutor) ExecuteTool(toolName string, args map[string]interface{}) (interface{}, error) {
    // 1. Look up tool(查找工具)
    tool, exists := e.registry.GetTool(toolName)
    if !exists {
        return nil, fmt.Errorf("tool not found(工具未找到): %s", toolName)
    }
    
    // 2. Execute(执行工具)
    result, err := tool.Execute(args)
    if err != nil {
        return nil, fmt.Errorf("tool execution failed(工具执行失败): %w", err)
    }
    return result, nil
}

// ===== Define Tools for OpenAI(定义OpenAI工具格式)=====

var tools = []openai.Tool{
    {
        Type: "function",
        Function: &openai.FunctionDefinition{
            Name:        "search_flights",
            Description: "Search for available flights by departure, destination, and date",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "from": map[string]interface{}{"type": "string", "description": "Departure city"},
                    "to":   map[string]interface{}{"type": "string", "description": "Destination city"},
                    "date": map[string]interface{}{"type": "string", "description": "Date YYYY-MM-DD"},
                },
                "required": []string{"from", "to", "date"},
            },
        },
    },
    {
        Type: "function",
        Function: &openai.FunctionDefinition{
            Name:        "get_tax_rate",
            Description: "Get airport fee and fuel surcharge for a route",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "from": map[string]interface{}{"type": "string"},
                    "to":   map[string]interface{}{"type": "string"},
                },
                "required": []string{"from", "to"},
            },
        },
    },
    {
        Type: "function",
        Function: &openai.FunctionDefinition{
            Name:        "calc_total_price",
            Description: "Calculate total price: base_price + airport_fee + fuel_fee",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "base_price":  map[string]interface{}{"type": "number"},
                    "airport_fee": map[string]interface{}{"type": "number"},
                    "fuel_fee":    map[string]interface{}{"type": "number"},
                },
                "required": []string{"base_price", "airport_fee", "fuel_fee"},
            },
        },
    },
}

// ===== Execute Tool Call(执行工具调用)=====

func executeToolCall(tc openai.ToolCall, executor *ToolExecutor) string {
    var args map[string]interface{}
    json.Unmarshal([]byte(tc.Function.Arguments), &args)

    result, err := executor.ExecuteTool(tc.Function.Name, args)
    if err != nil {
        return fmt.Sprintf(`{"error": "%s"}`, err.Error())
    }
    
    data, _ := json.Marshal(result)
    return string(data)
}

// ===== Main Loop(主循环)=====

func main() {
    ctx := context.Background()
    
    // Setup registry and executor(初始化注册中心和执行器)
    registry := NewRegistry()
    registry.Register(&SearchFlightsTool{})
    registry.Register(&GetTaxRateTool{})
    registry.Register(&CalcTotalPriceTool{})
    executor := NewToolExecutor(registry)
    
    // Initialize LLM client(初始化LLM客户端)
    client := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
    
    // User input(用户输入)
    userMsg := "帮我查5月30日北京到上海最便宜的机票,并算出含税总价"
    
    messages := []openai.ChatCompletionMessage{
        {Role: openai.ChatMessageRoleUser, Content: userMsg},
    }
    
    // ReAct Loop(推理-行动循环):最多5轮
    for i := 0; i < 5; i++ {
        // Call LLM(调用LLM)
        resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
            Model:    "gpt-4o",  // or "deepseek-chat" / "ollama/llama3"
            Messages: messages,
            Tools:    tools,     // Tell LLM what tools are available(告诉LLM有哪些工具)
        })
        if err != nil {
            log.Fatal("LLM call failed:", err)
        }
        
        msg := resp.Choices[0].Message
        messages = append(messages, msg)
        
        // No tool calls → final answer(没有工具调用,说明LLM给出了最终答案)
        if len(msg.ToolCalls) == 0 {
            fmt.Println("\n=== Final Answer(最终回答)===")
            fmt.Println(msg.Content)
            break
        }
        
        // Process each tool call(处理每个工具调用)
        for _, tc := range msg.ToolCalls {
            fmt.Printf("\n🧠 LLM called tool: %s\n", tc.Function.Name)
            fmt.Printf("   Arguments(参数): %s\n", tc.Function.Arguments)
            
            // Execute tool(执行工具)
            result := executeToolCall(tc, executor)
            fmt.Printf("   Observation(结果): %s\n", result)
            
            // Send result back to LLM(将结果返回给LLM继续推理)
            messages = append(messages, openai.ChatCompletionMessage{
                Role:       openai.ChatMessageRoleTool,
                Content:    result,
                ToolCallID: tc.ID,
            })
        }
    }
}

六、执行流程可视化

User Input(用户输入)
    ↓
LLM 推理 → 决定调哪个工具
    ↓
Tool Call(工具调用请求) → JSON 格式,包含工具名和参数
    ↓
Tool Executor(Go后端执行)
    ↓
Observation(结果返回给 LLM)
    ↓
LLM 继续推理 → 决定下一步或生成最终回答

用案例走一遍

步骤

LLM 思考

Tool Call

Observation(结果)

1

"先查航班"

search_flights(北京, 上海, 2026-05-30)

三个航班,最便宜MU456,票价650

2

"查税费"

get_tax_rate(北京, 上海)

机建费50,燃油费140

3

"算总价"

calc_total_price(650, 50, 140)

840

4

"生成回答"

MU456,票价650,机建50,燃油140,含税总价840元


七、核心认知

Tool Calling(工具调用)的本质不是 AI 技术,而是一种架构模式,把后端系统变成可被 LLM 调度的工具网络。

架构分层

层级

角色

说明

LLM

Scheduler(调度器)

决定调哪个工具、传什么参数

Tool Registry

Service Catalog(服务目录)

列出所有可用工具

Tool Executor

Execution Layer(执行层)

实际干活(查DB、调API、做计算)

Observation

Result Feedback(结果反馈)

把执行结果送回 LLM

与 MCP 的关系

MCP(Model Context Protocol,模型上下文协议)是 Tool Calling 的标准化升级版,把工具定义统一为 schema,通过 MCP Server 统一管理。


八、总结

这篇通过机票查询的案例,展示了如何用 Go 语言实现 Tool Calling(工具调用):

  1. 定义工具(Tool Registry):告诉 LLM 有哪些能力
  2. LLM 调度:LLM 决定调哪个工具、传什么参数
  3. 后端执行(Tool Executor):Go 代码实际干活
  4. 结果反馈(Observation):把结果返回 LLM 继续推理
  5. 循环直到完成(ReAct Loop)

写在你代码里的每一个函数(查航班、算价格、查税率)都变成"工具",LLM 看着工具列表决定"现在该用哪个",然后你的 Go 代码去执行,结果回来 LLM 再回答用户。这就是 Tool Calling 的核心价值。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐