Skip to content

第七章 工具调用

7.1 Function Calling 基础

在上一章中,我们深入探讨了检索增强生成(RAG)技术。RAG 通过向量检索为模型注入了外部知识,让 LLM 能够"看到"训练数据之外的信息——文档、手册、数据库记录。然而,RAG 本质上是一种"只读"能力:模型可以查询知识,却无法改变任何东西。它不能发一封邮件,不能下单采购,不能执行一段代码,也不能调用实时 API 获取此刻的天气。

想象一个场景:用户说"帮我查一下北京现在的天气,如果超过 30 度就给张经理发邮件提醒"。RAG 无法完成这个任务——它既不能获取实时天气,更不能发邮件。这就需要一种新的能力:让模型不仅能"看",还能"做"。

这就是本章的主题——工具调用(Tool Calling),也被称为 Function Calling。如果说 RAG 给了模型一双"眼睛"去阅读外部知识,那么 Function Calling 就是给模型装上了一双"手",让它能够实际操作外部系统。从"能说会道"到"动手做事",这是 Agent 从聊天机器人走向真正智能代理的关键一步。

本章将从 Function Calling 的基础概念出发,逐步深入工具定义、多平台对比、Agent 工具循环,最终涵盖 MCP 协议等前沿话题。

7.1.1 什么是 Function Calling

Function Calling(函数调用) 是大模型与外部世界交互的核心机制。它让模型在对话过程中,能够识别用户的意图,决定调用哪个外部工具,生成结构化的调用参数,由开发者的代码实际执行工具,再将执行结果返回给模型做最终回复。

用一个类比来理解:Function Calling 就是给 AI 装上手。

想象一个聪明但没有手脚的人坐在一个房间里。他能听懂你说的话,知道应该做什么,但他自己无法行动——他知道"北京今天很热"需要查天气,但他没有手机可以打开天气 App;他知道"帮我发邮件"需要用邮件系统,但他碰不到键盘。Function Calling 就是给这个聪明人装上了手:他可以拿起"手机"查天气,可以操作"键盘"发邮件,可以在"计算器"上做精确运算。

不过,有一个关键细节:模型本身并不执行工具。它只是决定"该调用什么工具、用什么参数",实际执行由开发者编写的代码完成。继续用上面的类比——聪明人的"手"是被外部控制的:他说"我要查北京天气",然后有人(开发者代码)帮他执行了查询,把结果递到他手里,他再根据结果给出回答。模型是"大脑",代码是"手",Function Calling 是连接两者的"神经"。

为什么需要 Function Calling?

传统 LLM 存在几个根本性限制:

  • 知识截止日期:训练数据有截止时间,无法获取实时信息(如天气、股价、新闻)
  • 无法执行操作:只能生成文本,不能真正"发邮件"或"下订单"
  • 缺乏精确计算:数学计算能力有限,不如专门的函数精确
  • 无法访问私有数据:无法直接查询企业内部数据库、API

Function Calling 优雅地解决了这些问题:模型识别用户意图后,生成结构化的函数调用请求,由开发者代码实际执行,再将结果返回模型做最终回复。整个过程中,模型负责"理解与决策",代码负责"执行与反馈",两者各司其职。

7.1.2 Function Calling 的工作原理

整个 Function Calling 流程分为五个关键步骤。让我们以"北京今天天气怎么样?"为例,走完整条链路。

┌──────────┐     ┌──────────────┐     ┌──────────────┐
│  用户输入  │ ──▶ │  LLM 推理决策  │ ──▶ │  生成 Tool Call │
└──────────┘     └──────────────┘     └──────┬───────┘

                    ┌──────────────────────────┘

            ┌──────────────┐
            │  执行函数(代码)  │
            └──────┬───────┘


┌──────────┐     ┌──────────────┐     ┌──────────────┐
│  最终回复  │ ◀── │  LLM 整合结果  │ ◀── │  返回函数结果  │
└──────────┘     └──────────────┘     └──────────────┘

图 7-1:Function Calling 完整流程

步骤一:定义工具(Define Tools)

开发者以 JSON Schema 格式定义工具,描述工具的名称、用途和参数。模型并不执行工具,它只是"阅读"这些描述,然后决定何时、是否调用。

python
# 定义一个天气查询工具的 JSON Schema 描述
weather_tool = {
    "type": "function",               # 声明这是一个函数类型的工具
    "function": {
        "name": "get_weather",        # 工具名称,模型据此生成调用
        "description": "获取指定城市的实时天气信息",  # 描述工具用途,模型据此判断何时调用
        "strict": True,               # 开启严格模式,确保参数 100% 符合 Schema
        "parameters": {               # 参数定义,使用 JSON Schema 规范
            "type": "object",         # 顶层是一个对象
            "properties": {           # 定义对象的各个属性
                "city": {             # city 参数:城市名
                    "type": "string",
                    "description": "城市名称,如 Beijing, Shanghai"
                },
                "unit": {             # unit 参数:温度单位(可选)
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],  # 限定可选值
                    "description": "温度单位"
                }
            },
            "required": ["city"],      # city 是必填参数
            "additionalProperties": False  # strict 模式要求:禁止额外字段
        }
    }
}

关键理解description 是最重要的字段。模型完全依赖描述来判断何时调用工具。一个模糊的描述(如"查询数据")会导致模型在错误的时机调用工具。

步骤二:发送请求(Send Request)

将工具定义和用户消息一起发送给模型:

python
from openai import OpenAI          # 导入 OpenAI SDK

client = OpenAI()                  # 初始化客户端,自动读取环境变量中的 API Key

response = client.chat.completions.create(  # 发起聊天补全请求
    model="gpt-4o",                # 使用支持 Function Calling 的模型
    messages=[                     # 消息列表
        {"role": "user", "content": "北京今天天气怎么样?"}  # 用户的提问
    ],
    tools=[weather_tool]           # 将工具定义传给模型,模型据此决定是否调用
)

步骤三:接收工具调用(Receive Tool Call)

如果模型判断需要调用工具,返回的 response 中会包含 tool_calls 而非普通文本回复:

python
# 模型返回的 tool_calls 结构
tool_calls = response.choices[0].message.tool_calls
# tool_calls[0].function.name      -> "get_weather"
# tool_calls[0].function.arguments -> '{"city": "Beijing", "unit": "celsius"}'
# tool_calls[0].id                -> "call_abc123"(用于后续关联结果)

注意 arguments 是一个 JSON 字符串,不是 Python 字典——使用前需要 json.loads() 解析。

步骤四:执行工具(Execute Tool)

开发者代码解析模型返回的参数,实际执行函数:

python
import json   # 用于解析模型返回的 JSON 参数字符串

def get_weather(city: str, unit: str = "celsius") -> dict:
    """模拟天气查询(实际项目应调用真实天气 API)"""
    return {
        "city": city,
        "temperature": 26,
        "unit": unit,
        "condition": "晴"
    }

# 遍历模型返回的所有工具调用(可能包含多个并行调用)
for tool_call in tool_calls:
    fn_name = tool_call.function.name             # 获取函数名,如 "get_weather"
    fn_args = json.loads(tool_call.function.arguments)  # 解析参数 JSON 字符串为字典
    result = get_weather(**fn_args)               # 用 ** 解包字典为关键字参数,实际执行函数

步骤五:返回结果(Return Results)

将工具执行结果返回给模型,让它生成最终的自然语言回复:

python
# 将工具调用结果追加到消息历史中
messages.append({
    "role": "tool",                          # 使用 tool 角色标记工具结果
    "tool_call_id": tool_call.id,            # 关联到对应的 tool_call,模型据此匹配结果
    "content": json.dumps(result, ensure_ascii=False)  # 将结果序列化为 JSON 字符串
})

# 再次调用模型,让它读取工具结果并生成最终回复
final_response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages                         # 此时消息包含:用户问题 + 模型工具调用 + 工具结果
)
# 最终输出:"北京今天天气晴朗,气温26°C"

这就是一个完整的 Function Calling 循环。回顾整个过程:用户提问 → 模型决定调用 get_weather → 开发者代码执行查询 → 结果返回模型 → 模型生成自然语言回答。模型从头到尾没有真正"执行"过任何函数,它只是做出了"调用决策"和"结果整合"。

7.1.3 JSON Schema 定义规范

OpenAI 在 2024 年 8 月引入了 strict: true 模式,这是生产环境的最佳实践。开启 strict 后,模型输出的工具调用参数 100% 符合 Schema 定义,不会出现类型错误、缺少必填字段等问题。

strict 模式的强制规则:

规则说明
additionalProperties: false每个 object 必须显式禁止额外字段
required 显式列出所有必填字段必须在 required 数组中
类型严格匹配不允许 string 输出成 number
enum 精确匹配不允许输出枚举之外的值

常用 JSON Schema 类型与约束:

python
# 一个完整的工具参数定义示例,展示了常用类型与约束
{
    "type": "object",               # 顶层为对象类型
    "properties": {
        "query": {                  # 字符串类型,带长度约束
            "type": "string",
            "description": "搜索关键词",
            "minLength": 1,         # 最短 1 个字符
            "maxLength": 200        # 最长 200 个字符
        },
        "limit": {                  # 整数类型,带数值范围
            "type": "integer",
            "description": "返回结果数量",
            "minimum": 1,           # 最小值
            "maximum": 50,          # 最大值
            "default": 10           # 默认值
        },
        "sort_by": {                # 字符串类型,带枚举约束
            "type": "string",
            "enum": ["relevance", "date", "popularity"],  # 只能从这三个值中选
            "description": "排序方式"
        },
        "filters": {                # 数组类型
            "type": "array",
            "description": "过滤条件",
            "items": {              # 数组元素的定义
                "type": "string"
            }
        }
    },
    "required": ["query"],          # query 是必填,其余可选
    "additionalProperties": False   # strict 模式要求:禁止额外字段
}

实践建议:始终在描述中给出示例值。例如 "description": "城市名称,如 Beijing, Shanghai""description": "城市名称" 效果好得多,因为模型会从示例中学习预期的格式。

7.1.4 OpenAI vs Anthropic vs 开源模型对比

不同模型的工具调用机制有显著差异,理解这些差异对跨平台开发至关重要。

设计哲学差异

维度OpenAIAnthropic Claude开源模型(Qwen/Llama)
设计理念工具是"插件",独立扩展工具是"对话的一部分",深度融入兼容 OpenAI 格式,各有特色
并行调用原生支持,默认开启支持,Claude 4 后可禁用Qwen 支持,Llama 依赖推理框架
调用格式tool_calls 数组tool_use 内容块多用 tool_calls 兼容格式
工具选择auto/none/requiredauto/any/tool 三级控制基本 auto/none
混合输出不支持(文本或工具调用二选一)支持(一段回复中同时含文本和工具调用)取决于具体实现

OpenAI 风格——插件式调用:

python
# OpenAI 工具调用返回格式:返回一个 tool_calls 数组
response.choices[0].message.tool_calls
# [
#   ToolCall(id="call_1", function=Function(
#       name="get_weather",
#       arguments='{"city":"Beijing"}'
#   ))
# ]

OpenAI 的设计将工具调用视为独立于文本回复的事件。一条助手消息要么是文本,要么是工具调用,二者不同时出现。

Anthropic 风格——对话流中的工具使用:

python
import anthropic  # 导入 Anthropic SDK

client = anthropic.Anthropic()   # 初始化客户端
response = client.messages.create(  # 发起消息请求
    model="claude-sonnet-4-20250514",  # 使用支持工具调用的 Claude 模型
    max_tokens=1024,              # 最大输出 token 数
    tools=[{                      # 工具定义(格式与 OpenAI 不同)
        "name": "get_weather",
        "description": "获取天气信息",
        "input_schema": {          # Anthropic 用 input_schema 代替 parameters
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名"}
            },
            "required": ["city"]
        }
    }],
    messages=[{"role": "user", "content": "北京天气怎么样?"}]
)

# Claude 返回的 content 是一个内容块列表,可同时包含文本和工具调用
for block in response.content:           # 遍历内容块
    if block.type == "tool_use":         # 找到工具使用块
        print(block.name)                # 输出: "get_weather"
        print(block.input)               # 输出: {"city": "北京"}

Anthropic 的设计将工具调用融入对话流。一条回复中可以同时包含自然语言文本和工具调用——例如 Claude 可以先说"让我帮你查一下",然后附带一个 tool_use 块。

开源模型(Qwen2.5)示例:

python
import ollama   # 导入 Ollama 本地推理框架

response = ollama.chat(           # 发起聊天请求
    model="qwen2.5:7b",           # 使用 Qwen2.5 7B 模型(本地运行)
    messages=[{"role": "user", "content": "北京天气怎么样?"}],
    tools=[weather_tool]          # 兼容 OpenAI 格式的工具定义
)

关键要点:Anthropic 的 tool_usetool_result 作为内容块嵌入对话流,不破坏 user↔assistant 的严格交替节奏。OpenAI 则使用独立的 tool role,形成更明显的"插件调用"模式。选择哪种方式取决于你的应用架构——需要更多上下文感知时用 Anthropic 风格,需要简洁的调用-执行分离时用 OpenAI 风格。

工具调用控制对比:

控制模式OpenAIAnthropic
自动选择tool_choice="auto"tool_choice={"type": "auto"}
禁止调用tool_choice="none"不传 tools 参数
强制调用任一tool_choice="required"tool_choice={"type": "any"}
强制调用指定工具tool_choice={"type": "function", "function": {"name": "x"}}tool_choice={"type": "tool", "name": "x"}

7.1.5 完整实战:天气查询 Agent

下面我们实现一个完整的天气查询 Agent,将前面五个步骤串联起来。这个 Agent 支持两个工具:天气查询和空气质量查询,并能够处理多工具并行调用。

python
import json                       # 用于 JSON 序列化与反序列化
import openai                     # OpenAI SDK
from typing import List, Dict, Any  # 类型注解

client = openai.OpenAI()          # 初始化 OpenAI 客户端

# ========== 第一步:定义工具 ==========
tools = [
    {
        "type": "function",       # 工具类型为函数
        "function": {
            "name": "get_weather",  # 天气查询工具
            "description": "获取指定城市的实时天气信息",
            "strict": True,        # 开启严格模式
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {     # 城市名参数
                        "type": "string",
                        "description": "城市名称,如 Beijing, Shanghai, Tokyo"
                    },
                    "unit": {     # 温度单位参数(可选)
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位,celsius(摄氏度) 或 fahrenheit(华氏度)"
                    }
                },
                "required": ["city"],           # city 必填
                "additionalProperties": False    # 禁止额外字段
            }
        }
    },
    {
        "type": "function",       # 第二个工具
        "function": {
            "name": "get_air_quality",  # 空气质量查询
            "description": "获取指定城市的空气质量指数(AQI)",
            "strict": True,
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称"
                    }
                },
                "required": ["city"],
                "additionalProperties": False
            }
        }
    }
]

# ========== 第二步:实现工具函数 ==========
def get_weather(city: str, unit: str = "celsius") -> Dict[str, Any]:
    """模拟天气查询(实际项目应调用真实天气 API)"""
    weather_data = {               # 模拟天气数据库
        "Beijing": {"temperature": 28, "condition": "晴", "humidity": 45},
        "Shanghai": {"temperature": 32, "condition": "多云", "humidity": 70},
        "Tokyo": {"temperature": 25, "condition": "小雨", "humidity": 80},
    }
    data = weather_data.get(city, {"temperature": 22, "condition": "未知", "humidity": 50})
    return {
        "city": city,
        "temperature": data["temperature"],
        "unit": unit,
        "condition": data["condition"],
        "humidity": data["humidity"]
    }

def get_air_quality(city: str) -> Dict[str, Any]:
    """模拟空气质量查询"""
    aqi_data = {                   # 模拟 AQI 数据
        "Beijing": {"aqi": 85, "level": "良"},
        "Shanghai": {"aqi": 55, "level": "良"},
        "Tokyo": {"aqi": 40, "level": "优"},
    }
    return aqi_data.get(city, {"aqi": 60, "level": "良"})

# 工具函数映射表:函数名 -> 可调用函数
available_functions = {
    "get_weather": get_weather,
    "get_air_quality": get_air_quality,
}

# ========== 第三步:执行对话循环 ==========
def run_agent(user_query: str) -> str:
    """Agent 主循环:接收用户提问,返回最终回复"""
    messages = [{"role": "user", "content": user_query}]  # 初始化消息列表

    # 第一次调用模型:模型决定是否需要调用工具
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,               # 传入可用工具列表
        tool_choice="auto"        # 让模型自动决定是否调用工具
    )

    response_message = response.choices[0].message  # 获取模型返回的消息
    tool_calls = response_message.tool_calls         # 提取工具调用列表

    if tool_calls:                # 模型决定调用工具
        messages.append(response_message)  # 将助手的工具调用消息加入历史

        # 执行每一个工具调用
        for tool_call in tool_calls:
            fn_name = tool_call.function.name             # 获取函数名
            fn_args = json.loads(tool_call.function.arguments)  # 解析参数

            print(f"🔧 调用工具: {fn_name}({fn_args})")

            fn_result = available_functions[fn_name](**fn_args)  # 执行函数

            # 将工具执行结果加入消息历史
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,  # 关联工具调用 ID
                "content": json.dumps(fn_result, ensure_ascii=False)
            })

        # 第二次调用模型:模型整合工具结果,生成最终自然语言回复
        final_response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages    # 此时包含完整的上下文
        )
        return final_response.choices[0].message.content

    # 如果模型没有调用工具,直接返回文本回复
    return response_message.content

# ========== 测试 ==========
if __name__ == "__main__":
    # 测试 1:单工具调用
    result1 = run_agent("北京今天天气怎么样?")
    print(f"🤖 回复: {result1}\n")

    # 测试 2:多工具并行调用(模型自动判断需要并行查询)
    result2 = run_agent("北京和上海的天气和空气质量分别怎么样?")
    print(f"🤖 回复: {result2}")

预期输出:

🔧 调用工具: get_weather({'city': 'Beijing', 'unit': 'celsius'})
🤖 回复: 北京今天天气晴朗,气温28°C,湿度45%。

🔧 调用工具: get_weather({'city': 'Beijing', 'unit': 'celsius'})
🔧 调用工具: get_weather({'city': 'Shanghai', 'unit': 'celsius'})
🔧 调用工具: get_air_quality({'city': 'Beijing'})
🔧 调用工具: get_air_quality({'city': 'Shanghai'})
🤖 回复: 北京天气晴,28°C,AQI 85(良);上海多云,32°C,AQI 55(良)。

注意第二个测试用例:当用户同时询问两个城市的天气和空气质量时,模型一次性返回了 4 个工具调用,开发者代码依次执行后将所有结果一并返回,模型再综合生成最终回复。这就是并行工具调用(Parallel Tool Calling),它显著提升了多工具场景的效率。

7.1.6 使用 LangChain 简化工具调用

上面的代码虽然清晰,但样板代码较多。LangChain 等框架对 Function Calling 做了封装,让工具定义更简洁:

python
from langchain_openai import ChatOpenAI       # LangChain 的 OpenAI 封装
from langchain_core.tools import tool          # @tool 装饰器
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from typing import List

# 使用 @tool 装饰器:自动从函数签名和 docstring 生成 JSON Schema
@tool
def search_documents(query: str, top_k: int = 5) -> str:
    """搜索内部知识库文档,返回最相关的内容。

    Args:
        query: 搜索关键词
        top_k: 返回结果数量,默认5条
    """
    docs = [                      # 模拟文档库
        "Python 3.12 引入了新的类型注解语法",
        "LangChain 是一个构建 LLM 应用的框架",
        "Function Calling 允许模型调用外部工具",
        "MCP 协议是 Anthropic 提出的上下文管理标准",
        "Agent 是能够自主决策和行动的 AI 系统"
    ]
    return "\n".join(docs[:top_k])

@tool
def calculate(expression: str) -> str:
    """执行数学计算,支持加减乘除和括号。

    Args:
        expression: 数学表达式,如 '2 + 3 * 4'
    """
    try:
        result = eval(expression)  # 注意:生产环境需用安全方式处理
        return f"计算结果: {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

# 创建 LLM 并绑定工具
llm = ChatOpenAI(model="gpt-4o", temperature=0)  # temperature=0 保证输出稳定
tools = [search_documents, calculate]
llm_with_tools = llm.bind_tools(tools)  # 将工具绑定到 LLM

# 对话
messages = [
    SystemMessage(content="你是一个有用的助手,可以使用工具来回答问题。"),
    HumanMessage(content="帮我搜索 LangChain 相关的文档,然后计算 156 * 23")
]

# 第一轮:模型决定调用工具
response = llm_with_tools.invoke(messages)
print("工具调用:", response.tool_calls)

# 执行工具并将结果加入消息
messages.append(response)        # 将模型的工具调用消息加入历史
for tool_call in response.tool_calls:
    tool_name = tool_call["name"]
    tool_args = tool_call["args"]

    # 查找并执行对应工具
    for t in tools:
        if t.name == tool_name:
            result = t.invoke(tool_args)  # 调用工具
            break

    messages.append(ToolMessage(content=result, tool_call_id=tool_call["id"]))
    print(f"🔧 {tool_name}: {result}")

# 第二轮:模型生成最终回复
final_response = llm_with_tools.invoke(messages)
print(f"🤖 最终回复: {final_response.content}")

LangChain 的 @tool 装饰器自动从 Python 函数的类型注解和 docstring 生成 JSON Schema,省去了手写工具定义的繁琐。这是从"原始 API"到"框架封装"的进步,后续章节中我们会看到更多框架对工具调用的进一步抽象。

7.1.7 常见误区

在学习和使用 Function Calling 时,初学者容易陷入以下几个误区:

误区一:模型会"执行"工具

"我定义了 get_weather 工具,模型应该会自己帮我查天气吧?"

事实:模型从不执行任何工具。它只生成调用意图(函数名 + 参数 JSON),实际执行完全依赖开发者编写的代码。如果你不写执行逻辑,工具调用就只是一段没人执行的 JSON。

误区二:工具越多越好

"我把所有 API 都注册成工具,让模型自己选。"

事实:工具过多会显著降低模型的决策准确率。研究表明,当工具数量超过 10-15 个时,模型选错工具的概率明显上升。最佳实践是按场景分组,动态注入相关工具集。

误区三:description 不重要

"函数名已经很清楚了,描述随便写写就行。"

事实description 是模型判断"何时调用工具"的唯一依据。一个模糊的描述会导致模型在不该调用时调用,或者在该调用时不调用。好的描述应该说明:工具做什么、何时该用、何时不该用。

误区四:strict 模式可有可无

"非 strict 模式也能用,干嘛多此一举?"

事实:非 strict 模式下,模型可能输出不符合 Schema 的参数(如类型错误、缺少字段、多余字段),导致代码执行时崩溃。生产环境中务必开启 strict: true

误区五:工具调用结果可以不返回给模型

"我执行完工具拿到结果了,直接返回给用户不行吗?"

事实:工具执行结果通常需要返回给模型进行整合和格式化。直接返回原始 JSON 给用户体验很差。模型擅长将结构化数据转化为自然语言——这正是它的强项。

7.1.8 本节小结

本节我们从"给 AI 装上手"这个类比出发,系统介绍了 Function Calling 的核心概念和工作原理:

序号核心要点
1Function Calling 是 LLM 与外部世界交互的桥梁。模型负责"理解与决策",代码负责"执行与反馈",Function Calling 是连接两者的"神经"
2五步流程:定义工具 → 发送请求 → 接收调用 → 执行工具 → 返回结果,构成一个完整的工具调用循环
3JSON Schema 是工具定义的通用语言,生产环境务必使用 strict: trueadditionalProperties: false
4OpenAI 采用"插件"设计,工具调用通过独立 tool_calls 数组和 tool role 实现
5Anthropic 将工具调用融入对话流,通过 tool_usetool_result 内容块实现,支持文本与工具调用混合输出
6开源模型(Qwen2.5/Llama 3.1+)大多兼容 OpenAI 工具调用格式,但细节有差异
7并行调用是现代模型的核心能力,模型可一次返回多个独立工具调用,显著提升效率
8LangChain 等框架通过 @tool 装饰器简化了工具定义,自动从函数签名生成 Schema

至此,我们理解了 Function Calling 的"是什么"和"怎么用"。但在实际项目中,工具不可能像示例那样写死在代码里——你需要一套系统化的方式来定义、注册和管理工具。7.2 节将深入探讨工具的定义与注册,包括如何用 Pydantic 规范参数验证、如何组织工具注册表、以及如何在多 Agent 场景下动态分发工具。