从 Function Calling 到评估:一次 LLM 测试工程化的真实实践
调通 Function Calling 后,我逐渐意识到,大模型并不是简单地“调用函数”,而是基于 function schema 做决策。本文反复修改 schema,并结合响应中的 reasoning_content 进行观察,梳理了模型在什么条件下会触发函数调用、在什么情况下直接回答。实践表明,description、required、properties 等 schema 设计,对模型行为的
前言
在上一篇文章里,我只是把 AI 大模型调通了。
也正是在那一步,我开始真正意识到:
大模型不是“什么都知道的黑盒”,而是有明确能力边界的系统。
比如很简单的问题:
现在几点?
现在天气怎么样?
直接问大模型,它是答不出来的。
不是它“傻”,而是它本身就不具备获取实时信息的能力。

那怎么办?
答案就是:Function Calling(函数调用)。
这篇文章不讲宏大概念,只记录一件事:
👉 我第一次把 Function Calling 跑通,并真正理解模型是“如何决定要不要调用函数”的。
一、什么是 Function Calling(先说人话)
Function Calling 本质只有一句话:
模型不会帮你执行函数,它只会“决定是否建议你调用函数”。
真正执行函数的,永远是你写的代码。
所以 Function Calling 并不是魔法,而是一套清晰的协作流程:
用户提问
↓
模型判断是否需要调用函数
↓
如果需要,返回结构化的 tool_calls
↓
本地代码执行函数
↓
把结果再喂回模型
↓
模型生成最终回答
理解这一点,是后面所有调试、测试、评估的前提。
二、最小 Demo:只做一件事——获取当前时间
为了避免干扰,我没有一上来做天气、接口请求这些复杂场景,
而是选择了一个最小、可验证、不会受外部影响的函数:
获取当前时间
functions.py
import datetime
def get_time():
now = datetime.datetime.now()
return {"time": now.strftime("%Y-%m-%d %H:%M:%S")}
function_scheme = [
{
"type": "function",
"function": {
"name": "get_time",
"description": "获取当前时间",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": ""
}
},
"required": []
}
}
}
]
这里有一个点,当时我并没有意识到它的重要性:
-
description -
properties -
required
后面我才发现,这三个字段,几乎决定了 Function Calling 的一切行为。
三、第一次调用模型:不要急着写逻辑,先看响应
main.py(第一次调用)
from zhipuai import ZhipuAI
from functions import function_scheme, get_time
import json
client = ZhipuAI(api_key="你的key")
messages = [
{"role": "user", "content": "现在几点?"}
]
response = client.chat.completions.create(
model="glm-4.5",
messages=messages,
tools=function_scheme,
tool_choice="auto"
)
message = response.choices[0].message
print(message)
这一行非常重要:
print(message)
一开始我只关注两件事:
-
有没有
tool_calls -
模型有没有返回结果
但后来我发现,真正该重点看的,是另一个字段。
四、关键转折:看懂 reasoning_content
模型返回中,有一个字段:
reasoning_content
第一次我几乎忽略了它。
直到我看到类似这样的内容,
好好仔细看看,这就是模型思路的过程:
用户问现在几点,这是一个关于当前时间的问题。
我需要使用 get_time 函数来获取当前时间。
查看函数参数:
- city: 可选参数
由于 city 不是必填参数,我可以直接调用函数。
看到这里慢慢就会明白:
模型不是“碰巧调用了函数”,
而是在严格阅读我写的 function_schema,然后做判断。
五、模型到底是怎么“决定要不要调用函数”的?
在我反复修改、对比之后,结论逐渐清晰:
1️⃣ description 决定“模型能不能想起你这个函数”
如果 description 写得模糊,
模型很可能根本不会考虑这个函数。
2️⃣ required 决定“是否需要追问用户”
-
required 里有字段 → 缺参数 → 模型倾向于追问
-
required 为空 → 参数可缺 → 模型可以直接调用
这是非常明确的“契约判断”。
3️⃣ properties 决定“模型敢不敢构造参数”
properties 描述越清晰,
模型越容易正确构造 arguments。
六、完整跑通 Function Calling 的闭环
当模型返回了 tool_calls 之后,接下来就不是“AI 的事”了,而是工程逻辑。
if message.tool_calls:
tool_call = message.tool_calls[0]
result = get_time()
messages.append(message.model_dump())
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
response2 = client.chat.completions.create(
model="glm-4.5",
messages=messages
)
print(response2.choices[0].message.content)
最终效果是:
用户:现在几点?
模型:我需要调用 get_time
工具:2026-01-09 10:32:01
模型:现在是 10 点 32 分
到这里,Function Calling 的最小闭环算是跑通了。
七、为什么这一步对“测试”和“评估”这么重要?
在真正理解这一套流程之前,我对 LLM 测试的理解是模糊的。
而现在,我突然明白了一件事:
Function Calling 的测试,本质不是测函数,
而是测模型的“决策行为”。
比如:
-
什么情况下会调用?
-
什么情况下不会调用?
-
是 schema 的问题,还是 prompt 的问题?
-
是 required 设计不合理,还是 description 描述有歧义?
这些问题,不看 reasoning_content,根本无从判断。
八、从 Function Calling,自然走向“评估”
当你开始关心这些问题时,其实已经走到下一步了:
-
行为是否稳定?
-
决策是否符合预期?
-
schema 微调后,调用概率如何变化?
-
prompt 改动是否真的有效?
这已经不再是“用不用 AI”,
而是如何评估一个 LLM 系统是否“可控、可测、可维护”。
后面的内容,我会继续围绕这个方向展开。
写在最后
这次 Function Calling 的实践,对我来说最大的收获不是代码本身,而是这一点:
只要 function_schema 没设计好,
怎么调 prompt,效果都很有限。
这是很多教程一笔带过,但在真实工程里极其关键的地方。
如果你和我一样,是从测试视角看 LLM,
那么 Function Calling,绝对值得你手敲一遍、反复改 schema、对照 reasoning 看行为变化。
这一步,真的会“一通百通”。
下一篇继续是Function Calling,第一次系统性评估 LLM 决策行为
更多推荐



所有评论(0)