Function Calling新手教程:从零开始掌握AI函数调用
Day 2:Function Calling 详解|从暴力匹配到语义理解,手写Agent工具调用
开篇
上一篇笔记发布后24小时内获得270次浏览——对于一个刚开始记录学习过程的普通开发者而言,这个反馈超出了预期。
今天继续深入Agent实战。
昨天梳理了Agent的四大核心要素:对话历史、工具调用、Function Calling、规划能力。今天的目标是——将这四个概念串联,搭建一个可直接运行的实用工具。
这个工具命名为「代码片段管理器」,核心功能是保存和检索常用代码片段,例如设计模式模板、工具方法等。
从“暴力匹配”入手
AI导师的建议很务实:先不要引入Function Calling,用最原始的字符串匹配写一版。只有亲手踩过坑,才能深刻理解Function Calling解决了什么问题。
于是第一版代码长这样:
class SnippetTools:
def parse_intent(self, text):
"""字符串匹配判断用户想干什么"""
if "保存" in text or "存" in text:
return "sa ve"
elif "搜索" in text or "找" in text or "查找" in text:
return "search"
elif "列表" in text or "看看" in text:
return "list"
elif "删除" in text:
return "delete"
else:
return "chat"
运行效果对比如下:
| 用户输入 | 识别结果 |
|---|---|
| "保存一下这段代码" | 识别为 sa ve |
| "存一下这段代码" | 识别为 sa ve |
| "帮我留着这段代码" | 识别失败,跑到chat去了 |
问题瞬间暴露:你永远无法穷举用户的所有表达方式。
"留着"、"记一下"、"收起来"、"帮我存下"……每新增一种说法就必须追加一个关键词。这正是暴力匹配方案最致命的缺陷。
Function Calling 的本质
一句话概括:让大模型自主决策该调用哪个工具,开发者不再需要编写堆叠式if-else。
对比两种方案的差异:
| 维度 | 暴力匹配(字符串匹配) | Function Calling |
|---|---|---|
| 识别方式 | 关键词匹配 | AI语义理解 |
| 灵活性 | 低,必须使用固定说法 | 高,支持多种自然语言表达 |
| 维护成本 | 高,需要持续增加关键词 | 低,AI自动理解意图 |
| 理解能力 | 换种说法即失效 | 任何说法均能理解 |
实现步骤
第一步:定义工具Schema
这是Function Calling的核心——通过JSON结构向AI声明可用的工具、每个工具的功能描述以及所需参数。
TOOLS_SCHEMA = [
{
"type": "function",
"function": {
"name": "sa ve_snippet",
"description": "保存一段代码片段到本地存储", # 这句话很重要!
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "要保存的代码内容"
},
"language": {
"type": "string",
"description": "编程语言,如 python、ja va",
"enum": ["ja va", "python", "ja vascript"]
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "标签,如 ['单例模式', '创建型']"
}
},
"required": ["code", "language"]
}
}
},
# ... 还有 search_snippet、delete_snippet 等
]
关键要点:description 写得越详尽,AI的决策准确率就越高。
优秀案例:
"description": "保存代码片段,参数包括代码内容、编程语言、标签和分类"
反面案例:
"description": "保存"
第二步:调用LLM,传入工具定义
response = client.chat.completions.create(
model="mimo-v2-flash",
messages=conversation_history,
tools=TOOLS_SCHEMA, # 关键:传入工具定义
tool_choice="auto" # 让AI自动决定是否调用工具
)
第三步:处理LLM返回的工具调用
message = response.choices[0].message# LLM说:我要调用 sa ve_snippet
if message.tool_calls:
for tool_call in message.tool_calls:
tool_name = tool_call.function.name # "sa ve_snippet"
arguments = json.loads(tool_call.function.arguments)
# arguments = {"code": "...", "language": "ja va", "tags": ["单例模式"]} # 执行工具
result = execute_tool(tool_name, arguments) # 把结果告诉LLM,让它生成最终回复
conversation_history.append({
"role": "tool",
"content": json.dumps(result)
})
第四步:LLM生成最终回复
# 再调一次LLM,这次带上工具执行结果
final_response = client.chat.completions.create(
model="mimo-v2-flash",
messages=conversation_history
)print(final_response.choices[0].message.content)
# 输出:代码已保存!ID: 1,分类:设计模式,标签:单例模式、创建型
完整调用流程
时序图
运行效果
用户: "帮我存一下这段Ja va代码"
小助手: "好的,请把代码发给我"用户:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}小助手: "代码已保存!
- ID: 1
- 语言: Ja va
- 标签: 设计模式、单例模式
- 文件: snippets.json"
无论是说“存一下”、“保存”、“帮我留着”还是“记下来”,LLM均能准确识别用户意图并触发相应工具。
今日核心要点
| 问题 | 答案 | 为什么需要 |
|---|---|---|
| Function Calling是什么? | 让大模型自行判断调用哪个工具,无需手动编写if-else | 暴力穷举关键词不可持续 |
| 核心是什么? | 工具Schema——用JSON描述工具的能力和参数 | 大模型必须知道可用工具列表 |
| description重要吗? | 至关重要!描述越清晰,AI决策越精准 | AI靠description理解工具的用途 |
| 调用流程是怎样的? | 用户输入 → LLM分析 → 执行工具 → LLM生成回复 | 完整的Agent交互闭环 |
常见疑问
Q1: Function Calling与普通API调用的区别是什么?
普通API调用是静态的:调用方在编码时已确定调用哪个接口。Function Calling则是动态的:LLM根据用户输入的语义,在运行时决定触发哪个工具。
Q2: 每个项目都需要单独编写工具Schema吗?
是的。每次向LLM发起请求时,必须传入tools参数,告诉LLM当前上下文中哪些工具可用。
Q3: 能否同时调用多个工具?
完全可以。LLM可以在一次响应中返回多个tool_calls,Agent会依次执行它们并将结果汇总返回。
明天预告
设计模式系列第一弹——单例模式。
后续计划每天学习一个设计模式,并结合今天搭建的代码片段管理器,边学边存,积累一个完整的模式库。预计一个月内系统掌握23种常用设计模式。
系列文章
- Day 1:Agent到底是什么?四个概念搞明白
- Day 2:Function Calling到底是什么?跟着AI老师从笨办法开始写
- Day 3:设计模式第一弹——单例模式(明天更新)



