跳到主要内容
📖 本章预览

本章为预览版本,展示部分核心内容。完整内容包含详细源码解析、实战代码和面试要点,加入知识星球即可解锁全部章节。

第13章 Function Calling:让 AI 不只会说,还会做

13.1 为什么需要 Function Calling

LLM 有一个致命缺陷:它只会"说",不会"做"。你问它"北京今天天气怎么样",它只能根据训练数据瞎猜,不能真的去查天气 API。你问它"帮我下个订单",它只能告诉你怎么下,不能真的帮你下。

Function Calling 解决的就是这个问题:让 LLM 能够调用外部函数/API,从"只会说"变成"能说能做"。

13.1.1 核心原理

传统方式(开发者硬编码意图识别):
用户:"查下北京天气" → if(包含"天气") → 调用天气API → 返回结果
问题:每加一个功能就要写一堆 if-else,无法处理模糊表达

Function Calling 方式(LLM 自动识别意图):
用户:"查下北京天气" → LLM 自动判断需要调用 getWeather → 自动生成参数 {"city":"北京"}
→ 框架执行函数 → 结果回传 LLM → LLM 生成自然语言回答

关键理解:LLM 不直接执行函数。它做的是两件事:

  1. 决定调用哪个函数(从你注册的函数列表中选)
  2. 生成函数参数(根据用户输入提取参数,输出 JSON)

真正执行函数的是框架(Spring AI),执行完后把结果喂回给 LLM,LLM 再基于结果生成最终回答。

13.1.2 完整执行流程

┌──────────┐     ①用户提问      ┌──────────┐
│ 用户 │ ──────────────→ │ Spring AI │
└──────────┘ └────┬─────┘
│ ②附带函数描述(JSON Schema)发给 LLM

┌──────────┐
│ LLM │
└────┬─────┘
│ ③返回 tool_calls:
│ [{name:"getWeather", args:{"city":"北京"}}]

┌──────────┐
│ Spring AI│ ④从注册表找到函数,反序列化参数,执行
└────┬─────┘
│ ⑤函数返回:{"temperature":28,"description":"晴"}

┌──────────┐
│ LLM │ ⑥基于函数结果生成自然语言回答
└────┬─────┘
│ ⑦"北京今天晴,气温28°C,适合出行。"

┌──────────┐
│ 用户 │
└──────────┘

注意第②步:Spring AI 会把你注册的函数自动转成 JSON Schema 描述发给 LLM,告诉它"你有这些工具可以用"。LLM 看到描述后自己决定要不要用、用哪个。

13.1.3 深入理解:LLM 到底是怎么"调用函数"的

很多人对 Function Calling 有一个误解:以为 LLM 真的"执行"了函数。实际上 LLM 什么都没执行,它只是生成了一段特殊格式的 JSON。

本质:Function Calling 是一种"结构化输出"的特殊形式。

普通对话时,LLM 输出自然语言文本:
"北京今天天气晴,气温28度。"

Function Calling 时,LLM 输出的是 JSON:
{"name": "getWeather", "arguments": {"city": "北京"}}

LLM 并不知道 getWeather 函数的代码是什么,它只是根据你给的函数描述(JSON Schema),
判断"用户的问题需要调用这个函数",然后按照 Schema 格式生成参数。

这就像你给一个人一份菜单(函数列表),他看了菜单后说"我要点宫保鸡丁"(选择函数+参数),
但他不会做菜——做菜的是厨房(你的应用代码)。
底层 HTTP 协议层面发生了什么(以 DashScope 为例):

第一次请求(用户问题 + 函数描述):
POST /api/v1/services/aigc/text-generation/generation
{
"messages": [
{"role": "user", "content": "北京天气怎么样"}
],
"tools": [
{
"type": "function",
"function": {
"name": "getWeather",
"description": "根据城市名称查询当前天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
}


🔒 解锁完整内容

本章剩余内容需要解锁后查看

以上仅为本章部分预览内容,完整内容包含更多深度源码解析、实战代码和面试要点。

加入知识星球你将获得:

  • ✅ 全部 26 章完整内容 + 持续更新
  • ✅ 配套源码 + 实战项目
  • ✅ 一对一答疑 + 面试辅导
  • ✅ 简历优化 + 内推机会

📚 本章完整目录

以下为本章完整目录结构,加入知识星球即可解锁全部内容。

13.2 三种函数注册方式

13.2.1 方式一:@Bean + Function 接口(最简单)

13.2.2 方式二:FunctionCallback(更灵活)

13.2.3 方式三:动态注册(运行时决定)

13.3 实战案例:旅行助手(多函数协作)

13.3.1 定义请求/响应模型

13.3.2 注册函数

13.3.3 旅行助手服务

13.3.4 Controller

13.4 参数校验与错误处理

13.4.1 防御式参数校验

13.4.2 函数执行异常处理

13.5 Function Calling 源码执行链路

13.5.1 为什么函数没被调用?排查清单

13.6 使用 ChatClient 的方式(推荐)

13.7 实战总结