LLM 问世之后,很长一段时间,人们发现了很多用例可以证明:
AI不行啊,不是人工智能,而是人工智障。
比方说,最经典的一个问题就是:
7.11 和 7.9 哪个数字更大?

你也可以修改我们第一节课【春哥的Agent通关秘籍02:搭建环境及语言选择】里的 check_env.py代码,问AI:"今天是几月几日?"
deepseek 会自信地回答你:

早期的AI对于这样的用例毫无防备,闹了很多笑话。
但是,在Agent时代,这些根本不是问题,你可以稳定解决所有加减乘除,可以准确比较大小,可以知道今天是几月几日。
比如,无论你去deepseek官网,或者是豆包,提问"今天是几月几日?"
都能得到准确而正确的回答:

这是为什么?
因为官网和APP都利用了我们今天要学习的内容:Function Calling。
工具调用。
一、Function Call的历史及现状
为了解决早期大语言模型无法做精细活儿,无法应对准确计算和实时信息获取的问题。
2023年6月13日 ,OPENAI 在发布 gpt-4-0613 和 gpt-3.5-turbo-0613 版本时,推出了一项轰动世界的设计和能力:
Function Calling。
发布这个功能之前
开发者(包括早期的 LangChain 用户)想要让 AI 调用工具,必须使用 ReAct 模式。
做法:你在 System Prompt 里写一大段话:“你是一个可以使用工具的 AI。如果你想用计算器,请输出 Action: Calculator, Input: 1+1。”
痛点:
-
不稳定:AI 经常忘了格式,或者多打了个标点符号。
-
解析难:开发者必须写复杂的正则表达式(Regex)去抓取 AI 的回复。
-
Token 浪费:你需要把工具的描述写在 Prompt 里,占用大量上下文。
OpenAI的创新
OpenAI 受Meta的Toolformer启发,通过微调(Fine-tuning),让模型在底层“原生”支持了调用外部工具的能力。
你不再需要在 Prompt 里求 AI “请按格式输出”。你只需要把 JSON Schema 传给 API,模型就能极度稳定地输出结构化的 JSON 调用指令。
可以说:Function Calling 是 OpenAI 将“学术界的 Tool Learning”转化为“工业级 API 标准”的产物。
它标志着 AI Agent 开发从“手搓 Prompt 的黑客时代”进入了“标准化工程开发时代”。现在,Anthropic (Claude)、Google (Gemini)、DeepSeek 等主流模型厂商都跟随并支持了这一标准。
二、Function Calling 基本流程
我画了一张图,给大家解释当用户输入“北京今天天气如何”之后,整个的架构流转:

你会发现一个重要的定律:
LLM 对于 Agent 而言,其实是无状态的。
当Agent朝LLM发起会话时,需要每次都携带历史记录,以完成多轮会话。
每次都需要携带注册到agent中的function列表,每次都需要写代system prompt。

你可以把它想象成电影《初恋50次》里的女主角,或者是一条只有 7 秒记忆的金鱼。 无论你们之前聊得多么热火朝天,当你发起的 HTTP 请求结束那一刻,它就把你彻底忘了。
因此,这带来了三个很大的负担问题:
- 人设负担
- 每次会话都要重新声明一次人设,不然LLM就会不知道该信息。
- 技能树负担
- 每次会话都要重新声明一次所有Agent拥有的全技能Json Schema,这会占用巨大的Token。
- 记忆负担
- 随着对话进行,messages 列表会越来越长。
这会导致以下几个问题:
- 费钱:LLM 是按 Input Token 收费的。第 1 句的历史,在第 100 轮对话时被重复计费了 100 次。
- 变慢:处理的文字越多,首字延迟(TTFT)越高。
- 溢出:一旦超过模型的上下文上限(比如 32k 或 128k),模型就会报错或强制截断,导致“失忆”。
- 变蠢:一旦token过长,LLM在巨大的token里关于会话里的关键信息的注意力会被稀释。
如何解决这个问题,是所有Agent开发必须面对的。这里不过多展开了,先提一下。

三、工具定义与注册
在让AI知道应该在何时调用工具的第一步,相当于你需要在每次会话里附带一个目录,告诉AI:
嗨,牢A,我这里有一个名叫
get_current_time的工具,它是个方法,作用是“获取当前准确的时间”。
这样,当没有记忆的LLM面对“当前准确时间是什么”的时候,它才会知道要调用这个工具。
很巧,上节课我们才介绍了 JSON Schema,这节课的 Function Calling 注册工具,也需要用到JSON Schema。
但它不是完全标准的 JSON Schema,你可以先看看结构:
tools = [
{
# 1. 类型:目前仅支持 "function"
"type": "function",
# 2. 函数定义主体
"function": {
# 2.1 (必填) 函数名
"name": "...",
# 2.2 (强烈推荐) 给 AI 看的函数说明书
"description": "...",
# 2.3 (必填) 参数定义,遵循 JSON Schema 标准
"parameters": {
"type": "object",
"properties": { ... },
"required": [...]
},
# (可选) DeepSeek/OpenAI 支持的严格模式,设为 True 强制输出符合 Schema
"strict": True
}
}
]
如果想找官方文档的话,国内开发者优先建议参考 deepseek 的 Function Calling 文档:api-docs.deepseek.com/zh-cn/guide…
其中提到:
用户需要设置 base_url="api.deepseek.com/beta" 来开启 Beta 功能
嗯,看到这里,我们可以默默地去修改一下我们 .env 文件的 BASE_URL 参数,以求获得更稳定的返回结构。
另外还可以参考:platform.openai.com/docs/guides…
让我们根据上面学到的,随手注册两个工具方法:
# 定义工具列表
tools = [
# 方法A:获取当前精准时间
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "获取当前精确时间",
"parameters": {"type": "object", "properties": {}}
}
},
# 方法B:计算两个数的乘积
{
"type": "function",
"function": {
"name": "calculate_multiply",
"description": "计算两个数的乘积",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "第一个数字"},
"b": {"type": "number", "description": "第二个数字"}
},
"required": ["a", "b"]
}
}
}
]
有了 Schema,如何添加到会话里去呢?
非常简单!
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools, # 【关键】把工具箱递给它
)
四、识别AI的返回,调用具体的方法
刚刚我们在请求里附带了tools清单。
那么,怎么识别AI在什么时候需要调用我们的tools呢?
很简单:
# 获取 AI 的回复消息
ai_msg = response.choices[0].message
print(f"ai_msg.tool_calls:{ai_msg.tool_calls}")
在ai_msg这个LLM 返回值信息里,我们需要重点关注 ai_msg.tool_calls 这个字段。
如果它是None或者空数组,则表示AI没有想要调用的Function,否则的话,即代表:
刚才这一次会话,AI没有得到结论,而是希望调用一个或者多个Function。
所以,我们可以先写出如下的大致逻辑:
if ai_msg.tool_calls:
print(f"🤖 AI 决定调用工具!")
# 必须把 AI 的这句“我想调用工具”加入对话历史
# 否则后面通过工具结果回复时,AI 会失忆
messages.append(ai_msg)
# === 你的代码介入:执行工具 ===
for tool_call in ai_msg.tool_calls:
print(f"🤖 AI 决定调用工具: {tool_call.function.name}")
func_name = tool_call.function.name
# 解析参数 (JSON 字符串 -> 字典)
func_args = json.loads(tool_call.function.arguments)
else:
# AI 觉得不需要工具,直接回答
return ai_msg.content
拿到方法名与方法参数后,接下来要做的就是执行方法,得到结果,这一点连刚接触编程的新手都知道怎么写了。
拿到结果后,只需要再次执行:
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
# === 第二轮交互 ===
final_response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
为什么这里的第二轮交互里还要带上 tools?
因为Agent并不知道LLM是否会再发起一次Function Calling,如果不携带tools,LLM就非常可能在遇到问题时无奈地瞎编或者报错。
所以,很显然,我们需要递归,或者循环。
这就涉及到了一个新的,深刻的,重要的,难以规避的核心问题:
在Agent开发中,应该如何统一AI的思考与行动,并巧妙设计这个循环。
下一节课再见!
下一步预告
下节课,我们将探索 Agent 编程的一个核心问题:
思考范式。
敬请期待!
