基于 Spring AI Function Calling 实现企业级 AI 工具中台
本章带你完成 阶段 3:「智工」AI业务工具集成平台 项目: 基于 Spring AI Function Calling 构建一个具备工具链设计、业务系统集成、工具治理、人机协同的 AI 工具中台。
建议先看清楚“本章会新增/改造哪些文件”,再开始写代码,避免边写边迷路。
src/main/java/com/example/chatbot/
├── AiChatbotApplication.java
├── config/
│ └── ToolFunctionConfig.java # ⭐ 本章新增:Function Calling 工具函数(Spring Function @Bean)
├── controller/
│ ├── ChatController.java # 基础聊天(历史章节)
│ └── MemoryChatController.java # ⭐ 本章改造:记忆 + 工具调用 + 流式接口
├── dto/
│ ├── ChatRequest.java
│ ├── ChatResponse.java
│ ├── MemoryChatRequest.java
│ └── MemoryChatResponse.java
└── service/
└── ConversationService.java # 会话历史管理(历史章节)ToolFunctionConfig 用 @Bean 暴露 Function<Req,Res> 工具(时间/数据库/文件/通知)。MemoryChatController 的非流式与流式接口都需要启用工具调用能力。sessionId(会话)+ message(用户输入)为核心,不需要为每个工具单独写 Controller。mkdir -p src/main/java/com/example/chatbot/config创建文件:src/main/java/com/example/chatbot/config/ToolFunctionConfig.java
package com.example.chatbot.config;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 工具函数配置
*
* 使用 @Tool 注解定义 AI 可以调用的工具函数
*/
@Component
public class ToolFunctionConfig {
/**
* 获取当前时间
*/
@Tool(description = "获取当前的日期和时间,返回格式化的时间字符串")
public String getCurrentTime() {
LocalDateTime now = LocalDateTime.now();
String formatted = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("🔧 工具调用: getCurrentTime() -> " + formatted);
return formatted;
}
/**
* 查询数据库
*/
@Tool(description = "查询数据库,获取用户、订单等数据的统计信息")
public String queryDatabase(
@ToolParam(description = "查询的内容,如'用户数量'、'今日订单'、'活跃用户'等") String query) {
System.out.println("🔧 工具调用: queryDatabase(\"" + query + "\")");
String result = switch (query.toLowerCase()) {
case "用户数量", "有多少用户", "用户总数" -> "系统目前有 10,523 个注册用户";
case "今日订单", "今天的订单", "订单数量" -> "今天共有 156 个订单";
case "活跃用户", "在线用户" -> "当前有 342 个活跃用户在线";
default -> "未找到相关数据";
};
System.out.println("✅ 返回: " + result);
return result;
}
/**
* 文件操作
*/
@Tool(description = "执行文件操作,如查看文件大小、检查文件是否存在等")
public String fileOperation(
@ToolParam(description = "操作类型,如'查看大小'、'检查存在'、'最后修改时间'") String operation,
@ToolParam(description = "文件名") String filename) {
System.out.println("🔧 工具调用: fileOperation(" + operation + ", " + filename + ")");
String result = switch (operation.toLowerCase()) {
case "查看大小", "文件大小", "size" -> "文件 " + filename + " 的大小为 2.5 MB";
case "检查存在", "是否存在", "exists" -> "文件 " + filename + " 存在";
case "最后修改时间", "修改时间", "modified" -> "文件 " + filename + " 最后修改时间: 2024-11-21 10:30:00";
default -> "不支持的操作: " + operation;
};
System.out.println("✅ 返回: " + result);
return result;
}
/**
* 发送通知
*/
@Tool(description = "发送通知消息给指定用户,支持邮件、短信等方式")
public String sendNotification(
@ToolParam(description = "接收通知的用户") String recipient,
@ToolParam(description = "通知消息内容") String message) {
System.out.println("🔧 工具调用: sendNotification(" + recipient + ", \"" + message + "\")");
String notificationId = "NOTIF-" + System.currentTimeMillis();
String result = "通知已发送给 " + recipient + ",通知ID: " + notificationId;
System.out.println("✅ 返回: " + result);
return result;
}
}@Component 标记为 Spring 组件,支持自动扫描@Tool 注解标记方法,让 AI 能够调用@ToolParam 注解描述参数,帮助模型理解参数用途🔧 工具调用 和返回内容,方便调试/api/memory-chat、/api/memory-chat/streampublic class MemoryChatController {
private static final Logger log = LoggerFactory.getLogger(MemoryChatController.class);
private final ChatModel chatModel;
private final ConversationService conversationService;
private final org.springframework.ai.chat.client.ChatClient chatClient; // ⭐ 支持工具
public MemoryChatController(
ChatModel chatModel,
ConversationService conversationService,
com.example.chatbot.config.ToolFunctionConfig toolFunctionConfig // ⭐ 注入工具配置
) {
this.chatModel = chatModel;
this.conversationService = conversationService;
// ⭐ 创建配置了工具的 ChatClient
log.info("🔧 正在配置工具函数...");
this.chatClient = org.springframework.ai.chat.client.ChatClient.builder(chatModel)
.defaultTools(toolFunctionConfig)
.build();
log.info("✅ ChatClient 已创建,工具函数已配置");
}
}/**
* 带记忆的聊天接口(非流式,支持工具)
*/
@PostMapping
public MemoryChatResponse chat(@RequestBody MemoryChatRequest request) {
try {
request.validate();
log.info("收到带记忆的聊天请求 [会话: {}]: {}",
request.sessionId(), request.message());
// 1. 保存用户消息
conversationService.addUserMessage(request.sessionId(), request.message());
// 2. 获取完整历史
List<Message> history = conversationService.getHistory(request.sessionId());
// 3. 使用 ChatClient 调用 AI(自动选择是否调用工具)
String aiMessage = chatClient.prompt()
.messages(history)
.call()
.content();
// 4. 保存 AI 回复
conversationService.addAssistantMessage(request.sessionId(), aiMessage);
int turnCount = history.size() / 2;
return MemoryChatResponse.success(
request.sessionId(),
aiMessage,
turnCount
);
} catch (IllegalArgumentException e) {
return MemoryChatResponse.error("参数错误: " + e.getMessage());
} catch (Exception e) {
log.error("处理聊天请求时发生错误", e);
return MemoryChatResponse.error("服务器错误: " + e.getMessage());
}
}如果已按第6章实现流式接口,需要使用 ChatClient 的流式 API 来支持 Function Calling。关键是在 stream() 调用前确保 ChatClient 已配置了工具函数。
@PostMapping(value = "/stream", produces = "text/event-stream")
public Flux<String> streamChat(@RequestBody MemoryChatRequest request) {
try {
request.validate();
conversationService.addUserMessage(request.sessionId(), request.message());
// 获取对话历史
List<Message> history = conversationService.getHistory(request.sessionId());
// ⭐ 使用 ChatClient 的流式 API(支持工具调用)
Flux<String> responseFlux = chatClient.prompt()
.messages(history)
.stream()
.content();
AtomicReference<String> fullResponse = new AtomicReference<>("");
return responseFlux
.doOnNext(content -> {
fullResponse.updateAndGet(cur -> cur + content);
log.debug("收到流式数据: [{}]", content);
})
.filter(content -> content != null && !content.isEmpty())
.doOnComplete(() -> {
String finalResponse = fullResponse.get();
conversationService.addAssistantMessage(request.sessionId(), finalResponse);
log.info("流式回复完成 [会话: {}],长度: {} 字符",
request.sessionId(), finalResponse.length());
})
.doOnError(error -> log.error("流式响应错误", error));
} catch (Exception e) {
log.error("处理流式聊天请求时发生错误", e);
return Flux.just("错误: " + e.getMessage());
}
}mvn spring-boot:runcurl http://localhost:8080/api/memory-chat/healthMemory Chat API is running! 🧠🔧curl -X POST http://localhost:8080/api/memory-chat \
-H "Content-Type: application/json" \
-d '{
"sessionId": "fc-001",
"message": "现在几点了?请告诉我准确的时间"
}'观察:
🔧 工具调用: getCurrentTime() 日志2024-11-22 11:30:45curl -X POST http://localhost:8080/api/memory-chat \
-H "Content-Type: application/json" \
-d '{
"sessionId": "fc-002",
"message": "系统有多少用户?"
}'预期:AI 回复中应包含“10,523 个注册用户”等内容,并打印 queryDatabase 调用日志。
curl -N -X POST http://localhost:8080/api/memory-chat/stream \
-H "Content-Type: application/json" \
-d '{
"sessionId": "fc-stream-001",
"message": "请用流式方式介绍一下当前时间,并告诉我系统有多少用户"
}'你将看到 data: 打头的多行 SSE 输出,同时后端仍会打印工具调用日志。
你已掌握「智工」AI业务工具集成平台的开发能力。 接下来,进入阶段 4:「知搜」企业智能检索问答系统, 学习如何实现企业级 RAG 知识库系统。
A:
@Tool 注解的写法,两种方式可以结合使用A:可以通过 ChatOptions 或 ChatClient 的配置精细控制允许的工具集合。
A:在工具函数中返回带错误信息的响应,让 AI 将错误解释给用户,同时在日志中记录异常。