🧰 AI Agent 核心进阶:Tool Use / Tool Calling (工具调用) 原理与面试指南

如果说大模型(LLM)是大脑,那么 Tool Use(工具使用/工具调用) 就是为这个大脑装上了可以触碰物理世界的“手和脚”。

在上一篇我们了解了基础的 Function Calling(函数调用)。但在目前的 AI Agent 面试中,面试官更喜欢用 “Tool Use” 这个词。这不仅是一个技术接口,更是智能体(Agent)解决复杂问题的核心行为模式

这篇文章将用大白话带你搞懂 Function Calling 和 Tool Use 的细微区别、常见工程架构,以及大厂极爱考的“工具调用容错机制”,附带保姆级实战代码!


在这里插入图片描述

💡 一、 Function Calling vs Tool Use:到底有啥区别?

很多初学者觉得这两个词是一回事,但在资深面试官眼里,它们代表了不同的业务抽象层级:

  • Function Calling(函数调用):偏向于底层 API 机制。比如 OpenAI 提供的 tools 参数,它的本质是让大模型输出一段结构化的 JSON 数据。它只是一个单纯的“参数提取器”。
  • Tool Use(工具调用):偏向于 Agent 的高级行为。它不仅包含了让模型输出 JSON,还包含了 Agent 拿到 JSON 后去执行代码、将报错信息捕获、将执行结果重新喂给模型、让模型继续思考的完整闭环(即 ReAct 循环)。

🌟 面试话术提炼
“Function Calling 是模型具备的一种能力(输出结构化参数);而 Tool Use 是 Agent 利用这种能力去和外部环境交互的工作流(Workflow)。”


⚙️ 二、 工具(Tool)的标准结构

不管是 LangChain、LlamaIndex 还是自己手搓 Agent,一个标准的“工具”都必须包含以下三个核心要素:

  1. Name(工具名称):必须是唯一的,通常只能包含字母、数字和下划线(如 web_searchpython_interpreter)。
  2. Description(工具描述)这是最重要的部分! 大模型完全依靠这段自然语言来决定什么时候该用这个工具。描述写得不好,大模型就会“乱用”或“不用”。
  3. Schema / Arguments(参数格式):定义这个工具需要接收什么参数(类型、是否必填等)。

🛡️ 三、 面试必考:Tool Use 的容错与防御机制

在生产环境中,把工具交给大模型是非常危险的。它可能会传错参数、漏传参数,或者本地的 API 直接挂掉。如何保证系统不崩溃? 这是面试的重中之重。

大厂核心防坑策略

  1. 绝不让大模型直接执行危险操作:对于删库、付款等敏感 Tool,必须在执行层加入 HITL(Human-In-The-Loop,人工确认) 节点。
  2. 捕获异常并“告诉”大模型:如果本地代码报错了(比如缺参数),千万不要直接把程序抛出 Exception 挂掉!要把报错信息(Error Traceback)转成字符串,作为 Tool 的 Observation(观察结果)返回给大模型,聪明的大模型会自动向用户追问缺失的参数,或者尝试换个参数重试。

🎯 四、 高频面试 Q&A 实战演练

Q1:各大模型厂商在 Tool Use 的底层实现上有什么区别?

标准答案

  • OpenAI 体系主要通过底层的 JSON Schema 强制约束,返回特定的 tool_calls 对象。
  • Anthropic (Claude) 早期和部分开源模型更倾向于使用 XML 标签(如 <tool_use>...</tool_use>)来进行工具调用。
  • 在工程落地时,我们通常会使用统一的中间件(如 LangChain 的 @tool 装饰器)来屏蔽这些底层模型的 API 差异。

Q2:如果系统里有 100 个工具,每次提问都要把 100 个工具的描述都发给大模型吗?

标准答案(高阶题)
绝对不行!这会导致 Token 严重超载,且大模型的注意力会分散,导致选错工具。
正确的架构是“工具检索(Tool Retrieval)”:把所有工具的描述(Description)向量化存入向量数据库。当用户提问时,先在向量库里检索出 Top-5 最相关的工具,然后再把这 5 个工具发给大模型做最终决策。

Q3:Agent 调用工具陷入“死循环”怎么办?(比如模型一直传错同一个参数)

标准答案
必须在系统层面设置硬性拦截机制

  1. 设置最大迭代次数(max_iterations),比如超过 5 次调用同一个工具失败,强制中断并回复用户“系统遇到技术困难”。
  2. 在工具执行层维护一个状态机,如果检测到连续两次传入完全相同的错误参数,直接在本地截断,强制要求模型向用户求助。

💻 五、 面试加分代码:手搓一个带容错机制的 Agent Tool

在面试中,用 Python 写一个优雅的 Tool 装饰器,并展示“如何优雅地把报错信息扔回给大模型”,会让你显得极具工程经验。

import json
import traceback
from typing import Callable, Any

# ==========================================
# 1. 定义一个通用的工具类 (Tool Wrapper)
# ==========================================
class AgentTool:
    """
    包装本地函数的工具类,专门处理异常并与 LLM 交互
    """
    def __init__(self, name: str, description: str, func: Callable):
        self.name = name
        self.description = description
        self.func = func # 真实要执行的 Python 函数

    def execute(self, **kwargs) -> str:
        """
        执行工具的核心逻辑,包含面试必考的“容错捕获机制”
        """
        print(f"🔧 [执行工具] {self.name} | 传入参数: {kwargs}")
        try:
            # 尝试执行本地真实的函数
            result = self.func(**kwargs)
            # 无论结果是什么,都转成字符串,因为大模型只认字符串文本
            return json.dumps({"status": "success", "result": result}, ensure_ascii=False)
        except TypeError as e:
            # 🎯 面试亮点:参数传错了?不要崩溃!把报错信息返回给大模型!
            error_msg = f"参数类型错误或缺失必需参数: {str(e)}。请检查你生成的参数并重试。"
            print(f"⚠️ [工具报错] {error_msg}")
            return json.dumps({"status": "error", "message": error_msg}, ensure_ascii=False)
        except Exception as e:
            # 捕获其他所有未知的业务异常
            error_trace = traceback.format_exc()
            return json.dumps({"status": "error", "message": f"工具执行遇到内部错误: {str(e)}"}, ensure_ascii=False)

# ==========================================
# 2. 定义真实的业务函数
# ==========================================
def search_stock_price(symbol: str, date: str = "today") -> float:
    """模拟查询股票价格的函数"""
    # 模拟一个常见的业务报错:用户没传必填参数,或者参数格式不对
    if not isinstance(symbol, str) or len(symbol) == 0:
        raise ValueError("股票代码(symbol)不能为空且必须是字符串!")
    
    # 模拟查库返回结果
    mock_db = {"AAPL": 150.5, "TSLA": 200.1}
    if symbol in mock_db:
        return mock_db[symbol]
    else:
        raise KeyError(f"数据库中未找到股票代码: {symbol}")

# ==========================================
# 3. 实例化我们的 Tool,并模拟 Agent 执行流
# ==========================================
stock_tool = AgentTool(
    name="search_stock_price",
    description="查询指定股票代码的实时价格,必须传入 symbol 参数。",
    func=search_stock_price
)

if __name__ == "__main__":
    # --- 场景 1:大模型很聪明,传对了参数 ---
    print("--- 场景 1:正常调用 ---")
    # 假设这是大模型通过 JSON 解析出来的参数字典
    llm_generated_args_1 = {"symbol": "AAPL"} 
    observation_1 = stock_tool.execute(**llm_generated_args_1)
    print(f"返回给 LLM 的观察结果 (Observation): {observation_1}\n")

    # --- 场景 2:大模型犯蠢了,传错了参数名字 (面试核心展示点) ---
    print("--- 场景 2:大模型传错参数 ---")
    # 假设大模型幻觉了,把 symbol 传成了 company_name
    llm_generated_args_2 = {"company_name": "Apple"} 
    observation_2 = stock_tool.execute(**llm_generated_args_2)
    print(f"返回给 LLM 的观察结果 (Observation): {observation_2}\n")
    
    # 💡 面试讲解要点:
    # 告诉面试官:“在场景 2 中,由于参数不对,Python 会抛出 TypeError。
    # 但由于我们的 AgentTool 做了 try-except 封装,程序并没有崩溃。
    # 我们把错误文本转成了 JSON 扔回给大模型。
    # 大模型看到 '参数类型错误' 的提示后,它会在下一个回合中自我纠正(Self-Correction),重新输出正确的 {'symbol': 'AAPL'}!”
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐