Tools
Tool Calling(工具调用) 或 Function Calling(函数调用),说的是:模型在回答过程中,不直接给最终自然语言,而是先输出“我想调用某个工具,并附带参数”这一结构化意图
- 模型负责决定:要不要调用工具、调用哪个工具、传什么参数
- 程序负责执行:真正去调用函数 / API / 数据库 / 业务服务,并把结果再送回模型
一句话定义: Tool 是外部能力本身,Tool Calling 是模型发起使用这项能力的过程。
| 概念 | 是什么 | 核心职责 |
| Tool | 一个被封装好的外部能力 | 负责“做事” |
| Tool Calling / Function Calling | 模型输出结构化调用意图的机制 | 负责“发起调用请求” |
| Agent | 会推理、会规划、会多步决策的智能体 | 负责“决定什么时候调用、调用几次、按什么顺序调用” |
理解:
- Tool 像工具箱里的“螺丝刀、扳手、计算器、天气接口”
- Tool Calling 像“我现在决定要用哪把工具,并说出参数”
- Agent 像“有判断能力的工人或调度者”,它会自己决定先用哪个工具、后用哪个工具、要不要继续调用
基本流程步骤:
- 用户提出问题
- 程序把“用户消息 + 工具定义”一起发给模型
- 模型判断是否需要调用工具
- 如果需要,模型返回
tool_calls - 程序根据
tool_calls真正执行工具 - 程序把工具结果再放回消息流
- 模型基于工具结果生成最终自然语言回复
工具定义方式:
1. 使用@tool装饰器
- 把一个普通 Python 函数包装成 LangChain Tool
- 让它具备
name、description、args等元信息 - 让它能够被模型或 Agent 识别并调用
案例:
from langchain_core.tools import tool
# @tool 装饰器:不写参数时,工具名默认为函数名 add_number,description 取自下方 docstring
@tool
def add_number(a: int, b: int) -> int:
"""两个整数相加""" # add_number.description
return a + b
# 直接执行工具:invoke 接收参数字典,键为参数名,值为参数值(与函数签名对应)
result = add_number.invoke({"a": 1, "b": 12})
print(result)
print(f"{add_number.name=}\n{add_number.description=}\n{add_number.args=}")
| 属性 | 作用 | 初学阶段怎么理解 |
| name | 工具名 | 模型要调用谁,首先看这个名字 |
| description | 工具说明 | 模型判断“什么时候该用这个工具”的最重要依据 |
| args | 参数结构 | 模型知道“应该传什么参数、参数是什么类型” |
| return_direct | 是否直接把结果返回给用户 | 主要在 Agent 场景更常见 |
从工程实践上说,定义 Tool 时最怕的不是“函数实现难”,而是:名字取得太随意,描述写得太模糊,参数定义不清楚

参数schema和Pydantic
有时候参数类型和格式不够清晰;参数校验不够严格。希望能表达
- 这个参数具体是什么意思
- 有没有取值范围
- 是否允许为空
- 参数传错时怎样更清晰地报错
这就是 args_schema 和 Pydantic 出场的原因
Pydantic定义
Pydantic 是 Python 里非常常用的数据校验库,本质是把“参数结构、参数类型、字段说明、校验规则”统一收敛到一个模型类里
入门阶段可先把握一点:Pydantic = 类型声明 + 自动校验 + 更清晰的参数说明
from langchain_core.tools import tool
from loguru import logger
from pydantic import BaseModel,Field
# Pydantic 模型:定义“工具参数接口”,字段 description 会进入工具参数 schema
class FieldInfo(BaseModel):
"""定义加法运算所需的参数结构"""
a: int = Field(description="第1个参数")
b: int = Field(description="第2个参数")
# args_schema=FieldInfo:把参数模型绑定到工具,模型会更清楚看到 a、b 的类型与说明
@tool(args_schema = FieldInfo)
def add_number(a: int, b: int) -> int
"""计算两个整数之和"""
return a + b
# 打印工具属性:带 args_schema 时,args 中会包含 Field 的 description
logger.info(f"name = {add_number.name}")
logger.info(f"args = {add_number.args}")
logger.info(f"description = {add_number.description}")
logger.info(f"return_direct = {add_number.return_direct}")
# 调用工具:传入字典,Pydantic 会做类型校验与转换
res = add_number.invoke({"a": 1, "b": 2})
logger.info(res)
"""
name = add_number
args = {'a': {'description': '第1个参数', 'title': 'A', 'type': 'integer'}, 'b': {'description': '第2个参数', 'title': 'B', 'type': 'integer'}}
description = 计算两个整数之和
return_direct = False
3
"""
- 用
BaseModel定义参数结构 - 用
Field(description=...)给参数写说明 - 用
@tool(args_schema=...)把参数模型绑定给工具
定义天气查询工具
准备工作:
在openwearth中申请api key.然后填到.env中
OPENWEATHER_API_KEY=你的密钥
定义好 Tool 后,还需要把它交给模型,这通常通过 bind_tools(...) 完成, 只有先把工具绑定给模型,后续模型调用时才有机会返回 tool_calls。也就是说,bind_tools([get_weather]) 的含义不是“现在就执行工具”,而是:
把 get_weather 这项能力声明给模型,告诉它:你之后如果判断有必要,可以调用这个工具
完整链路代码
from dotenv impot load_dotenv
load_dotenv(encoding="utf-8")
import os
from langchain_core.output_parsers import JsonOutputKeyToolsParser,StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from loguru import logger
from QueryWeatherTool import get_weather
# 初始化大模型(教程 5.4:需可调用工具的大模型)
llm = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 将工具绑定到模型:请求时会把 get_weather 的名称、描述、参数 schema 发给模型,模型随后可能返回 tool_calls
llm_with_tools = llm.bind_tools([get_weather])
# 解析器:从模型输出中提取“命中的天气工具参数”,得到可直接传给 get_weather 的入参
parser - JsonOutputKeyToolsParser(key_name=getweather.name, first_tool_only=True)
# 天气查询链:用户问题 → 模型(可能返回 tool_calls)→ 解析出参数 → 执行 get_weather → 得到天气 JSON 字符串
get_weather_chain = llm_with_tools | parser | get_weather
# 输出链:把天气 JSON 塞进提示词,由模型转成更适合用户阅读的自然语言描述
output_prompt = PromptTemplate.from_template(
"""你将收到一段 JSON 格式的天气数据{weather_json},请用简洁自然的方式将其转述给用户。
以下是天气 JSON 数据:
请将其转换为中文天气描述,例如:
"北京现在天气:多云,气温 28℃,体感有点闷热(约 32℃),湿度 75%,微风(东南风 2 米/秒),
能见度很好,大约 10 公里。建议穿短袖短裤。适合做户外运动。"
"""
)
output_parser = StrOutputParser()
output_chain = output_prompt | llm | output_parser
# 完整链:先拿到天气 JSON,再包装成 {"weather_json": x} 送入输出链,得到最终中文描述
full_chain = get_weather_chain | (lambda x: {"weather_json": x}) | output_chain
result = full_chain.invoke("请问北京今天的天气如何?")
logger.info(result)

组织方式:
- tool.py / tools/:定义给模型看的工具入口
- service.py / services/:写真实业务逻辑
- client.py / adapters/:封装第三方 API 或内部接口访问
- prompt / chain / agent 层:决定什么时候调用工具
tool设计原则:
- 职责单一:一个 Tool 最好只做一件清晰的事
- 输入明确:参数含义、类型、是否必填要说清楚
- 输出稳定:返回结构尽量稳定,不要时而返回字符串、时而返回复杂嵌套对象
- 异常可控:报错要能被程序捕捉和处理,不要直接把底层异常糊给用户
- 幂等与副作用隔离:能做查询就不要顺手做写入;涉及状态变更时,尽量拆成“先确认、再执行”的显式步骤
- 超时、重试与限流:外部 API 类 Tool 要设置超时、重试上限和频控,避免 Agent 在异常场景下反复调用
- 权限受控:涉及写操作、支付、删除、外呼等工具,一定要额外设防
- 可观测:最好能记录调用日志、参数、耗时、错误信息,便于排障

浙公网安备 33010602011771号