在智慧社区项目中,我尝试用 Spring AI 集成了通义千问大模型,实现了一个 AI 导购助手。本文记录如何通过 Function Calling 让大模型“学会”调用商品查询等业务接口,并结合 Redis 二级缓存与 SSE 流式响应,在成本和体验之间取得平衡。
一、背景:AI 导购不能只会简单“聊天”
最开始接入大模型时,我让它直接回答用户的问题,比如“推荐一些低糖水果”。大模型会根据训练数据生成一段看起来很合理的回答,但问题在于——它推荐的“低糖水果”可能根本不在我的商品库里。(我是齐夏,我要开始说谎了)
要让 AI 真正成为导购助手,它必须能获取实时、真实的业务数据。这就需要让大模型在需要时,主动调用我们提供的业务接口。
Function Calling 正是为此而生。
二、什么是 Function Calling?
Function Calling 是各大模型厂商提供的一种能力:大模型根据用户输入,判断是否需要调用外部函数,如果需要,就返回一个结构化的 函数调用请求(JSON 格式),由应用层去执行真正的业务逻辑,最后将执行结果再喂给大模型,生成最终的自然语言回复。
流程如下:
用户: "推荐低糖水果"
↓
大模型: 判断需要调用商品查询函数
↓ 返回函数调用请求
{
"name": "recommendProducts",
"arguments": { "requirement": "低糖水果" }
}
↓
应用层: 执行 recommendProducts(requirement) 查询数据库/ES
↓ 返回商品列表
[
{ "name": "圣女果", "price": 12.9, "sugar": "低" },
{ "name": "蓝莓", "price": 29.9, "sugar": "低" }
]
↓
大模型: 将商品列表组织成自然语言回复
↓
用户: "为您推荐以下低糖水果:圣女果(12.9元/盒)、蓝莓(29.9元/盒)……"这样一来,大模型就不会回答不存在的东西,从“只会聊天的机器人”升级为“能调用业务系统的智能助手”。
哇,既然如此好用!那么到底该如何实现呢?会不会很复杂...?
下文会给出答案
三、Spring AI 中的实现
Spring AI 对 Function Calling 提供了非常简洁的封装,我们只需要定义一个普通的 Spring Bean,并在方法上标注 @Tool 注解即可。
3.1 定义工具函数
@Component
public class ProductTools {
private final ProductService productService;
public ProductTools(ProductService productService) {
this.productService = productService;
}
@Tool(description = "根据用户需求推荐商品,返回商品名称、价格和糖分含量")
public List<ProductDto> recommendProducts(String requirement) {
// 调用业务层查询商品(可能是数据库或 Elasticsearch)
List<Product> products = productService.searchByRequirement(requirement);
return products.stream()
.map(p -> new ProductDto(p.getName(), p.getPrice(), p.getSugarContent()))
.toList();
}
public record ProductDto(String name, BigDecimal price, String sugarContent) {}
}关键点:
@Tool注解中的description非常重要,它会被发送给大模型,帮助模型理解这个函数何时该调用、参数含义是什么。方法签名会自动生成 Function Schema,无需手动拼接 JSON。
3.2 在对话中注册工具
@Service
public class AiAssistantService {
private final ChatClient chatClient;
private final ProductTools productTools;
public AiAssistantService(ChatClient.Builder builder, ProductTools productTools) {
this.chatClient = builder.build();
this.productTools = productTools;
}
public String chat(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.tools(productTools) // 注册工具函数
.call()
.content();
}
}当用户发送“推荐低糖水果”时,Spring AI 会自动:
将
ProductTools中所有@Tool方法的描述发送给大模型。大模型若决定调用
recommendProducts,Spring AI 会反射执行该方法。将执行结果自动回传大模型,并获取最终回答。
整个过程对开发者透明,只需关注业务逻辑的实现。
四、二级缓存:让高频问题不再“烧钱”
大模型 API 是按 Token 计费的。如果每个用户都问一遍“几点开门”,每次都调用大模型,token如流水,供不起啊。
于是我在 AI 模块中设计了一个 二级缓存策略:
实现逻辑:
public String chatWithCache(String userMessage) {
// 1. 一级缓存:预设模板匹配
String preset = presetCache.match(userMessage);
if (preset != null) {
return preset;
}
// 2. 二级缓存:Redis 查询
String cacheKey = "ai:qa:" + DigestUtils.md5Hex(userMessage);
String cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 3. 缓存未命中,调用大模型
String response = chatClient.prompt()
.user(userMessage)
.tools(productTools)
.call()
.content();
// 4. 存入 Redis,过期时间 24 小时
redisTemplate.opsForValue().set(cacheKey, response, 24, TimeUnit.HOURS);
return response;
}这样可以极大减少宝贵的token。(防御性编程:守护自己的钱袋子)
五、SSE 流式响应:让等待不再焦虑
大模型生成回答通常需要 2~5 秒,如果采用同步请求,用户会盯着空白页面等待,体验很差。
流式响应还是很常见的,平时用到的大模型都是如此,如果你喜欢神秘感自己可以不用,不过为了用户体验,加上!
Spring AI 支持 流式响应(Streaming),基于 SSE(Server-Sent Events)协议,可以逐字将生成内容推送到前端。
Controller 实现:
@GetMapping(value = "/api/ai/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.tools(productTools)
.stream() // 关键:使用 stream() 而非 call()
.content()
.map(chunk -> "data: " + chunk + "\n\n"); // SSE 格式
}前端接收(JavaScript):
const eventSource = new EventSource('/api/ai/chat/stream?message=推荐水果');
eventSource.onmessage = (event) => {
document.getElementById('output').innerHTML += event.data;
};
eventSource.addEventListener('done', () => eventSource.close());流式响应的 首字延迟 通常在 500ms 以内,用户能立刻看到 AI 正在“打字”,心理等待时间大幅缩短。
六、存在的局限与后续计划
目前仅集成了商品查询一个工具,运营端文案生成等工具还在开发中。
大模型偶尔会出现“幻觉”,返回不在缓存也不在函数调用范围内的回答,后续计划引入 RAG(检索增强生成) 进一步约束回答范围。(请关注后续玩转AI内容)
成本仍需控制,考虑对用户每日调用次数做限制。
七、总结
Function Calling 是连接大模型与业务系统的桥梁。通过 Spring AI 的简洁封装,我在社区项目中快速落地了一个可用的 AI 导购模块,并通过缓存和流式响应优化了成本与体验。
这让我深刻体会到:AI 落地的关键不在于模型本身有多强,而在于如何将它无缝嵌入现有的业务系统,并在性能、成本、体验之间找到平衡。
关于 AI 的一点随想:
AI 是一个强大的工具,但工具的价值在于使用它的人,重要的是你如何应用他,不要害怕会被他取代,业务才是Java!
欢迎交流讨论
评论交流
欢迎留下你的想法