给小雅装"手":工具调用(含完整可运行代码)
从原理到一份你能直接复制运行的 function calling 代码
上一节小雅会上网查了。你扔给它一道账:
"机票 800,酒店住 3 晚、每晚 400,一共多花多少?"
它张口就来:"大概 1800 吧。" ——错了(正确 800 + 400×3 = 2000)。大模型天生不擅长精确计算,它是"语感"在估,不是真算。
这一节,我们彻底搞懂"给智能体装手"的原理,然后——给你一份能直接复制、立刻跑起来的 Python 代码,亲手让小雅学会调用工具。学完,你不只是"会配插件",而是真正理解、并能写出工具调用。
一、原理:模型只"点菜",不"做菜"
最关键、也最反直觉的一点:调用工具时,大模型自己并不执行那个工具。 它只做两件事——
- 决定:该调用哪个工具、传什么参数;
- 读结果:工具被执行后,拿到返回值,再组织成人话。
真正去按计算器、发起搜索的,是模型外面的程序(你的代码 / 平台)。
小雅像一个自己不按计算器、但知道"这题得用计算器"的人:他喊一声"帮我算 800+400×3",旁边的助手按下计算器得 2000,递给他,他才说"一共 2000"。模型负责动脑,工具负责动手。
一张图看懂工具调用的来回
这套"决定 → 执行 → 喂回 → 作答"的来回,就是 function calling(工具调用) 的全部。
二、🔧 上手:一份能直接跑的工具调用代码
下面这段 Python 可以直接复制运行(用 DeepSeek 或任意 OpenAI 兼容接口)。它给小雅装了一个"计算器"工具,完整跑通上面那张图的四步。
准备:
pip install openai,把你的KEY换成你的 API Key。
# pip install "openai>=1.0"
import json
from openai import OpenAI
client = OpenAI(
api_key="你的KEY",
base_url="https://api.deepseek.com", # 用别家就换成对应地址
)
MODEL = "deepseek-chat"
# ① 真正干活的工具(模型不会执行它,是我们来执行)
def calculator(expression: str) -> str:
# 注意:真实项目别用 eval,这里仅作教学演示
try:
return str(eval(expression))
except Exception:
return "算式无效" # 模型偶尔传来非法算式,兜底别让程序崩
# ② 给模型的"工具说明书":名字 + 用途 + 参数
tools = [{
"type": "function",
"function": {
"name": "calculator",
"description": "做精确数学运算。输入一个算式字符串,返回数值结果。",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "要计算的算式,如 800+400*3"}
},
"required": ["expression"],
},
},
}]
def ask(user_input: str) -> str:
messages = [{"role": "user", "content": user_input}]
# 第一次请求:模型可能决定"要调用工具"
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[0].message
msg.tool_calls:
messages.append(msg)
tc msg.tool_calls:
args = json.loads(tc.function.arguments)
result = calculator(**args)
()
messages.append({
: ,
: tc.,
: result,
})
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[].message
msg.content
__name__ == :
(ask())
运行结果大概长这样:
[执行] calculator({'expression': '800+400*3'}) = 2000
这趟一共多花 2000 元(机票 800 + 住宿 400×3=1200)。
逐行对应那张图:定义工具(②说明书) → 模型决定调用(①) → calculator(**args) 执行(②返回) → role:"tool" 喂回(③) → 第二次请求得到人话(④)。这 40 行,就是一切"智能体工具调用"的内核——所谓框架,不过是把它包装得更顺手。
三、案例:再给它加一只"查汇率"的手
工具可以有好几只。把下面这个工具加进 tools 列表、并在执行处加一个分支,小雅就能边算边查汇率了:
# 新增工具:查汇率(演示用固定汇率,真实项目改成调用汇率 API)
RATES = {"USD": 7.2, "EUR": 7.8, "JPY": 0.048}
def exchange_rate(currency: str) -> str:
return str(RATES.get(currency.upper(), "未知"))
# 加进 tools 列表(写法同 calculator)
tools.append({
"type": "function",
"function": {
"name": "exchange_rate",
"description": "查询某货币兑人民币的汇率。参数 currency 如 USD/EUR/JPY。",
"parameters": {
"type": "object",
"properties": {"currency": {"type": "string"}},
"required": ["currency"],
},
},
})
# 在执行处按工具名分发(替换原来只有 calculator 的那行):
def run_tool(name, args):
if name == "calculator": return calculator(**args)
if name == "exchange_rate": return exchange_rate(**args)
return "未知工具"
现在问它"我在日本花了 30000 日元,折合人民币多少?"——小雅会先查汇率(0.048)、再调计算器(30000×0.048),自己挑对了两只手。这就是"工具箱"的威力。
四、工具的"说明书"决定它会不会用
模型靠你写的 description 和 parameters 来判断"何时用、怎么传"。写好说明,比换更强的模型还管用:
| 工具说明 | 模型的反应 |
|---|---|
❌ "description": "处理一些东西" | 不知道何时用 → 干脆不用 |
✅ "description": "做精确数学运算,输入算式字符串返回数值" | "要算数就用它!" |
- 别用
eval上生产:示例里为省事用了eval(且未做异常处理),但它能执行任意代码=安全大洞。真实项目要用安全的表达式库,或把工具关进沙箱(第 3 章会讲)。 - 工具不是越多越好:塞几十个工具,模型会挑花眼、选错。贵精不贵多,每个说明都写清"什么时候该用我"。
五、🙌 不想写代码?低代码一样能配
代码理解了原理;想快速出活,扣子里点几下就行:
- 进小雅编排页 → 插件 / 工具,添加一个计算器类插件(它已有联网搜索)。
- 问它"查下北京到上海高铁票价,买 3 张一共多少钱"。
- 看它先搜票价、再调计算器——和上面代码做的是同一件事,只是平台替你写好了那 40 行。
📸 低代码界面随版本变化,按"找到对应功能"操作;上线版会配截图。
这是我的 function calling 代码(贴上面那段)。请帮我再加一个『查询城市天气』的工具:给出工具的 JSON schema 定义、对应的 Python 函数(先用假数据),以及在分发处怎么接上。
这一节你不只懂了工具调用的原理,还亲手跑通了一份能用的代码——40 行看穿智能体的"手"是怎么长出来的,再也不觉得它神秘。
但你可能发现:任务一复杂(好几步、还互相依赖),小雅就有点毛躁——想到哪做到哪,漏步骤、也不复查。
下一节,我们教它两手老练功夫:先列计划再动手、做完自己检查一遍,并同样给你可运行的实现。小雅要从"能干"迈向"靠谱"了。
