Day 29 / 共 68 天 · 阶段 5 工具与 MCP
Function Calling:让只会说话的模型学会"动手"
阶段 4(RAG)你让模型"能查资料"。今天进入阶段 5,解决另一个天生缺陷:大模型只会吐文字,不会查实时天气、不会算精确数学、不会发邮件。Function Calling(函数调用)就是那座桥——让模型说出"我要调哪个函数、传什么参数",由你的代码去真正执行,再把结果回填给它。这是从"聊天机器人"迈向"能干活的 Agent"的分水岭;明天(Day 30)学怎么把这些工具写得安全可靠。
📍 你在阶段 5(工具与 MCP D29-32)的位置
D29 Function Calling→
D30 写安全工具→
D31 MCP 入门→
D32 多步工具链
💡 用一个类比兜住今天(今天全程沿用「餐厅里点菜」的世界观)
Function Calling = 顾客(模型)在餐厅点菜。大模型 = 只会动嘴、不会进厨房的顾客;工具/函数 = 后厨真正会做菜的厨师;菜单(工具定义) = 你递给顾客的菜单,写清有哪些菜、每道菜需要点明什么(辣度、份量);模型返回"要调用哪个函数+参数" = 顾客说"我要一份宫保鸡丁,微辣";你的代码执行函数 = 厨师照单做菜;结果回填给模型 = 菜端上桌,顾客尝过再决定下一步(接着点甜品 or 结账)。关键认知:模型只负责"点单",从不亲自下厨——执行永远是你的代码干的。
L01
模型的天生缺陷:只会吐文字
🤔 痛点问 GPT"北京现在几度?"它要么瞎猜一个数,要么说"我无法获取实时信息"。问"3897×4213 等于几",它也可能算错。为什么这么强的模型连这都不行?
💡 本质回忆 Day10:大模型的本事只有一个——预测下一个字。它没有联网能力、没有计算器、不能读你本地文件。它像个博学但被关在小黑屋里、只能说话的顾问:知识渊博,但够不到外面的世界。要让它"动手",必须给它配上能通向外部的"手"。
这些模型天生做不了的事,叫需要 工具(tool) 的任务:查实时数据、精确计算、读写数据库/文件、调别的 API、发消息……凡是"光靠脑补文字答不了、得真去做点什么"的,都要工具。
L02
核心思路:模型只负责"点菜"
🤔 痛点可模型连联网都不会,怎么可能让它"查天气"?难道要重新训练一个会上网的模型?
💡 本质巧妙之处:不让模型自己动手,只让它"说出想调哪个函数、传什么参数"。真正的执行由你的代码完成。就像顾客不进厨房,只说"来份宫保鸡丁"——做菜是厨师(你的代码)的事。模型输出的不再是给用户的话,而是一张结构化的"点菜单"(函数名 + 参数),你的程序照单执行。
👶 那模型怎么知道该点哪道菜你事先给它一份"菜单"(下一讲的工具定义),写清有哪些函数、各要什么参数。模型读用户问题 + 菜单,自己判断"这个问题该点
get_weather 这道菜,参数城市填北京"。它擅长的正是这种"理解意图 + 挑对工具 + 填对参数"——本质还是它最拿手的文字理解,只是输出格式变成了结构化的调用请求。L03
先给模型一份"菜单":工具定义
🤔 痛点模型又不认识你写的函数,它怎么知道你有个
get_weather 能查天气、该传什么参数?💡 本质调用模型时,你附上一份工具定义(tool schema)——用 JSON 描述每个函数:叫什么名、干什么用、需要哪些参数、参数什么类型。这就是递给顾客的"菜单"。模型读这份菜单,才知道能点什么、怎么点。描述写得越清楚,模型点得越准(明天 Day30 细讲怎么写好)。
{
"name": "get_weather",
"description": "查询某个城市当前的天气。当用户问天气、气温、冷热时使用。",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名,如 北京、上海" }
},
"required": ["city"]
}
}
📝 举个例子:description 决定模型点不点这道菜
若把 description 写成模糊的
"天气相关",模型遇到"我出门要带伞吗"可能就想不到调它;写清 "当用户问天气、气温、冷热、是否下雨、要不要带伞时使用",模型一读就知道这道菜正合适。工具描述本质是"写给模型看的说明书",是 Function Calling 成败的关键——明天专门讲怎么写。L04
完整闭环:五步点菜流程
🤔 痛点模型返回"要调 get_weather"之后呢?它自己会执行吗?结果怎么变成给用户的回答?
💡 本质Function Calling 是一个五步闭环,而且模型要被调用两次:第一次让它"点菜",你执行完把结果喂回去,第二次让它"根据菜品说人话"。看懂下面这张图,你就懂了整个机制。
图注:模型两头出现(②点菜、⑤说人话),中间③的"执行"永远是你的代码干的。这个"想→调→看结果→再想"的雏形,正是明天之后 Agent 主循环的核心(Day33)。
📝 举个例子:为什么非得调模型两次
如果只调一次到②就停,你拿到的是一句冷冰冰的机器指令
get_weather(city="北京"),用户看不懂。必须执行完(③得到 3℃)、把结果回填(④)、再让模型跑第⑤步,它才会把 3℃ 组织成人话"北京现在 3℃,挺冷的,记得穿厚点"。第一次调是"决策",第二次调是"表达"——缺了第二次,工具结果就没人翻译成给用户的答案。L05
一段能跑的最小示例
🤔 痛点五步闭环用代码写出来到底长啥样?会不会很复杂?
💡 本质其实就几十行。核心是一个
if:模型这次回复是"要调工具"还是"给最终答案"?若是前者,执行工具、把结果塞回消息列表,再问模型一次。下面用 OpenAI 风格 SDK 演示(各家大同小异)。import json
from openai import OpenAI # 回忆 Day11:调 LLM 的 SDK
client = OpenAI()
# 1) 这是我们真正的函数(厨师)——这里用假数据演示
def get_weather(city):
return {"city": city, "temp": 3, "desc": "晴"}
# 2) 菜单:告诉模型有这道工具(就是 L03 那份 schema)
tools = [{"type": "function", "function": {
"name": "get_weather",
"description": "查询城市当前天气。用户问冷热/气温/天气时用。",
"parameters": {"type": "object",
"properties": {"city": {"type": "string", "description": "城市名"}},
"required": ["city"]}}}]
msgs = [{"role": "user", "content": "北京现在几度?"}]
# 3) 第一次调模型:带上菜单,看它点不点菜
r = client.chat.completions.create(model="gpt-4o-mini", messages=msgs, tools=tools)
choice = r.choices[0].message
if choice.tool_calls: # 模型决定点菜了
call = choice.tool_calls[0]
args = json.loads(call.function.arguments) # 模型填的参数,如 {"city":"北京"}
result = get_weather(**args) # 4) 你的代码真去执行
msgs.append(choice) # 把"模型点的菜"记进对话
msgs.append({"role": "tool", "tool_call_id": call.id, # 把执行结果回填
"content": json.dumps(result, ensure_ascii=False)})
# 5) 第二次调模型:让它根据工具结果说人话
final = client.chat.completions.create(model="gpt-4o-mini", messages=msgs)
print(final.choices[0].message.content) # → "北京现在 3℃,天气晴,挺冷的"
else:
print(choice.content) # 模型觉得不用工具,直接答
👶 参数是模型"填"的,靠谱吗模型根据用户的话自动填参数(把"北京"填进 city),大多数时候很准,但不能盲信:它可能填错、填缺、甚至编一个不存在的城市。所以你的函数必须自己校验参数、处理异常——这正是明天 Day30「写安全工具」的核心。永远假设模型传来的参数可能是错的。
L06
三个常见误区
🤔 痛点刚学 Function Calling,新手最容易想岔哪几点?
💡 本质提前戳破三个误区,能帮你少走弯路。
| 误区 | 真相 |
|---|---|
| "模型会自己执行函数" | 不会。模型只输出"想调谁+参数",执行 100% 是你的代码。它连你函数里写了啥都不知道。 |
| "配了工具模型就一定会用" | 不一定。它自己判断该不该用、用哪个。描述写得烂,它可能视而不见或点错菜。 |
| "一次就搞定" | 典型要调模型两次(点菜+说人话);复杂任务还会连着点好几道菜(Day32 的多步工具链)。 |
👶 小白:那 Function Calling 和昨天学的 RAG、Agent 到底啥关系?听着有点像。
👨🏫 老师:好问题,理清这条线你就通透了。RAG(阶段4)是让模型"能查资料"的一种特定套路(检索→塞进提示词);Function Calling(今天)更通用——让模型能调任意工具(查天气、算数、发邮件、甚至去做一次检索)。而 Agent(阶段6)= 把 Function Calling 放进一个循环里:模型点菜→执行→看结果→再决定点下一道菜……反复直到任务完成。所以今天学的是 Agent 的"发动机零件",后面 Day33 会把它装进整台车。
L07
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 大模型天生做不了哪类事?为什么需要工具?
- Function Calling 里,模型负责什么、你的代码负责什么?(点菜 vs 下厨)
- 工具定义(schema)包含哪几块?为什么 description 特别重要?
- 五步闭环是哪五步?为什么模型通常要被调用两次?
- 为什么不能盲信模型填的参数?
✋ 动手 10 分钟:给模型配一把"计算器"
不想花 API 钱?先纯手工模拟一遍五步闭环,把机制吃透(零依赖、零成本):
import json
# 一个真工具:安全的加法计算器
def add(a, b):
return a + b
# 假装"模型点了菜"——真实中这段是模型返回的
fake_model_output = '{"name": "add", "arguments": {"a": 3897, "b": 4213}}'
call = json.loads(fake_model_output) # 解析模型的"点菜单"
if call["name"] == "add": # 路由到对应函数
result = add(**call["arguments"]) # 你的代码执行
print(f"工具返回:{result}") # → 8110(精确,模型自己算可能错)
# 想象:把这个 result 再喂回模型,让它说"3897+4213=8110"
进阶(有 API key 时选做):把 L05 完整代码跑通,把 get_weather 换成真的天气 API,体验模型自动填城市参数。
明日预告 · Day 30:模型会点菜了,但"菜"(工具)本身写得好不好,直接决定 Agent 靠不靠谱。明天学怎么写安全的工具:工具描述 schema 怎么写模型才点得准、为什么工具要幂等(重复调用不出乱子)、出错时为什么要返回结构化错误而不是抛异常崩掉。还会带你去《Claude Code 源码精讲》看工业级工具是怎么设计的。