Spring AI Function Call:让大模型真正调用外部工具
大模型本身只能根据已有上下文生成内容。
如果用户问:
帮我查一下今天的天气
模型自己不会访问天气接口。
Function Call 解决的就是:
让模型决定调用什么工具,由应用程序真正执行工具。
1. Function Call 是怎么工作的?
完整流程只有四步:
用户问题 + 工具列表
↓
LLM
↓
toolName + arguments
↓
应用执行工具
↓
Tool Result
↓
LLM
↓
最终回答
比如模型可能返回:
{
"toolName": "getWeather",
"arguments": {
"city": "北京"
}
}
应用再真正执行:
getWeather("北京");
所以最重要的一点是:
模型负责决策,应用负责执行。
模型本身不会真的调用数据库、浏览器或第三方 API。
2. Spring AI 的 @Tool
Spring AI 可以直接把 Java 方法声明成工具:
@Tool(description = "获取当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now().toString();
}
调用时注册:
chatClient.prompt()
.user(message)
.tools(new DateTimeTools())
.call()
.content();
Spring AI 会把这个方法转换成模型能理解的:
工具名
+
工具描述
+
参数 JSON Schema
然后发送给模型。
3. 工具参数靠 JSON Schema
例如:
@Tool(description = "查询指定时区的时间")
String getTime(
@ToolParam(description = "时区")
ZoneId zoneId) {
...
}
Spring AI 会自动生成参数 Schema。
模型因此知道:
参数叫什么
参数是什么类型
参数有什么含义
然后自动构造参数并发起 Tool Call。
这和 Structured Output 很像:
Structured Output
Java 类型 → Schema → 约束输出
Function Call
Java 参数 → Schema → 约束工具参数
4. description 本质也是 Prompt
例如:
@Tool(description = "时间工具")
就比较模糊。
更好的写法是:
@Tool(
description = "当用户询问当前日期或时间时调用,返回当前系统时间"
)
因为模型就是根据这些描述决定:
要不要调用?
调用哪个?
参数怎么填?
所以:
工具描述不是普通注释,而是工具选择阶段的 Prompt。
5. @Tool 和 ToolCallback
Spring AI 有两种工具定义方式。
简单场景:
@Tool
适合自己编写的普通 Java 方法。
复杂场景:
MethodToolCallback
FunctionToolCallback
适合:
第三方 SDK
动态工具
动态 Schema
运行时生成工具
可以简单记成:
@Tool
自动挡
ToolCallback
手动挡
最终都会变成统一的 ToolCallback。
6. ToolContext:传递业务身份
有些参数应该让模型生成:
city
keyword
jobName
但有些参数不能让模型决定:
userId
tenantId
权限
当前用户
这时候可以使用 ToolContext:
@Tool(description = "创建任务")
String createTask(String name, ToolContext context) {
String userId =
(String) context.getContext().get("userId");
return taskService.create(userId, name);
}
调用时注入:
.toolContext(Map.of(
"userId", userId
))
于是参数可以分成:
LLM Controlled
模型生成
Application Controlled
程序注入
像用户 ID、权限、租户信息应该尽量由应用控制。
7. ReAct:连续调用多个工具
复杂 Agent 往往不是调用一次工具就结束。
可能是:
思考
↓
搜索岗位
↓
得到结果
↓
继续思考
↓
查询岗位详情
↓
最终回答
这就是:
Reason
↓
Act
↓
Observation
↓
Reason
也就是 ReAct。
生产环境自己控制 ReAct Loop,可以增加:
最大调用次数
异常处理
超时
Token 统计
工具耗时
Tracing
避免模型一直重复调用工具进入死循环。
8. 多 Agent 要做工具隔离
不同 Agent 应该拥有不同的工具。
岗位 Agent
→ searchJob
→ getJobDetail
任务 Agent
→ createTask
→ deleteTask
采集 Agent
→ openPage
→ click
→ extract
不要把全部工具都交给所有 Agent。
因为:
工具集本身就是 Agent 的能力边界。
工具越精准,模型选错工具的概率也越低。
9. MCP 和 Function Call
可以简单理解:
Function Call
解决模型怎么调用工具
MCP
解决工具怎么被标准化提供和接入
未来 Agent 的工具可能来自:
本地 @Tool
+
远程 MCP Server
这样 Agent 的能力就可以动态扩展。
最终理解
整个调用链可以记成:
User
↓
Agent
↓
Prompt + Tools
↓
LLM
↓
toolName + arguments
↓
Tool Execution
↓
Tool Result
↓
LLM
↓
Final Answer
最值得记住的一句话:
Function Call 不是让大模型执行代码,而是让大模型决定“该调用什么工具”;真正的执行、权限、安全和异常处理始终由应用程序掌控。