我的AI开发之旅:从零开始用Spring AI Alibaba构建智能应用
作为一名有着3年Spring Boot开发经验的Java程序员,当领导安排我负责给我们的电商系统加上AI客服功能时,说不慌是假的。毕竟之前我最多就是调用过几个REST API,对AI这块完全是小白。经过两个多月的摸爬滚打,我想和大家分享一下使用Spring AI Alibaba的真实体验,以及踩过的那些坑。
为什么我最终选择了Spring AI Alibaba?
第一印象:这不就是Spring吗?
记得当时我在GitHub上找Java AI框架,看到LangChain4j、Spring AI、Spring AI Alibaba这几个选择时,脑袋都大了。但是当我看到Spring AI Alibaba的第一个示例代码时,瞬间有了亲切感:
@RestController
public class ChatController {
@Autowired
private ChatClient chatClient;
@PostMapping("/chat")
public String chat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.getMessage())
.call()
.content();
}
}
这不就是我平时写的Spring Boot代码吗?@Autowired、@RestController这些注解都是老朋友了,完全不需要重新学习什么架构设计。
配置简单到让人怀疑
我最怕的就是复杂的配置,还记得第一次配置Spring Security时被各种XML配置搞得头晕脑胀。但Spring AI Alibaba的配置简单得让我怀疑:
spring:
ai:
alibaba:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-plus
temperature: 0.7
就这样?对,就这样!添加依赖、写个配置文件、注入个Bean,5分钟就能跑起来一个AI聊天接口。
我的第一个AI应用:智能客服机器人
需求背景
我们的电商平台每天有大量的用户咨询,主要是订单查询、退换货政策、商品信息等重复性问题。老板希望先用AI处理这些标准问题,复杂问题再转人工。
第一版:最基础的问答
@Service
public class CustomerServiceBot {
@Autowired
private ChatClient chatClient;
public String handleQuestion(String question) {
String systemPrompt = """
你是一个专业的电商客服助手。
请礼貌、准确地回答用户问题。
如果遇到不确定的问题,请建议联系人工客服。
""";
return chatClient.prompt()
.system(systemPrompt)
.user(question)
.call()
.content();
}
}
这个版本跑起来后,效果还不错,但很快就发现问题了:每次对话都是独立的,AI记不住用户之前说了什么。
第二版:加上对话记忆
@Service
public class CustomerServiceBot {
@Autowired
private ChatClient chatClient;
// 简单的内存存储,生产环境建议用Redis
private final Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
public String handleQuestion(String userId, String question) {
List<Message> history = conversationHistory.computeIfAbsent(userId, k -> new ArrayList<>());
// 添加用户消息到历史
history.add(new UserMessage(question));
// 构建完整的对话上下文
String response = chatClient.prompt()
.system(getSystemPrompt())
.messages(history)
.call()
.content();
// 添加AI回复到历史
history.add(new AssistantMessage(response));
// 保持历史记录不要太长,避免token消耗过多
if (history.size() > 20) {
history.subList(0, history.size() - 20).clear();
}
return response;
}
private String getSystemPrompt() {
return """
你是一个专业的电商客服助手。
用户信息:
- 平台:XX电商
- 服务范围:订单查询、退换货、商品咨询、物流信息
回答规则:
1. 保持礼貌和专业
2. 答案要准确简洁
3. 不确定的问题建议转人工
4. 记住之前的对话内容
""";
}
}
这个版本好多了,但我发现一个严重问题:所有数据都存在内存里,应用重启就丢了,而且多实例部署时会有问题。
第三版:引入Redis持久化
@Service
public class CustomerServiceBot {
@Autowired
private ChatClient chatClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CONVERSATION_KEY_PREFIX = "conversation:";
private static final int CONVERSATION_TTL = 3600; // 1小时过期
public String handleQuestion(String userId, String question) {
String conversationKey = CONVERSATION_KEY_PREFIX + userId;
// 从Redis获取历史对话
List<Map<String, Object>> historyData = (List<Map<String, Object>>)
redisTemplate.opsForValue().get(conversationKey);
List<Message> history = historyData != null ?
convertToMessages(historyData) : new ArrayList<>();
// 添加用户消息
history.add(new UserMessage(question));
try {
String response = chatClient.prompt()
.system(getSystemPrompt())
.messages(history)
.call()
.content();
// 添加AI回复
history.add(new AssistantMessage(response));
// 保存到Redis
saveConversationToRedis(conversationKey, history);
return response;
} catch (Exception e) {
log.error("AI调用失败", e);
return "抱歉,我暂时无法回答您的问题,请稍后再试或联系人工客服。";
}
}
private void saveConversationToRedis(String key, List<Message> messages) {
// 只保留最近10轮对话
if (messages.size() > 20) {
messages = messages.subList(messages.size() - 20, messages.size());
}
List<Map<String, Object>> historyData = convertToMapList(messages);
redisTemplate.opsForValue().set(key, historyData, CONVERSATION_TTL, TimeUnit.SECONDS);
}
}
进阶功能:工具调用(Function Calling)
客服机器人能聊天了,但用户问订单信息时,它只能说”请提供订单号”,不能真正查询数据库。这时候我发现了Spring AI Alibaba的一个强大功能:Function Calling。
实现订单查询功能
@Component
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Description("根据订单号查询订单详细信息")
public OrderInfo getOrderInfo(
@Description("订单号") String orderNumber) {
Order order = orderRepository.findByOrderNumber(orderNumber);
if (order == null) {
return new OrderInfo("未找到该订单", null, null, null);
}
return new OrderInfo(
order.getStatus(),
order.getCreateTime(),
order.getTotalAmount(),
order.getItems().stream()
.map(item -> item.getProductName() + " x " + item.getQuantity())
.collect(Collectors.joining(", "))
);
}
@Description("查询物流信息")
public String getLogisticsInfo(@Description("订单号") String orderNumber) {
// 调用物流API
return logisticsService.getTrackingInfo(orderNumber);
}
}
@Service
public class SmartCustomerService {
@Autowired
private ChatClient chatClient;
public String handleQuestionWithTools(String userId, String question) {
return chatClient.prompt()
.system(getSystemPrompt())
.user(question)
.functions("getOrderInfo", "getLogisticsInfo") // 注册可用的工具
.call()
.content();
}
}
现在用户说”我想查一下订单123456的情况”,AI会自动调用getOrderInfo函数查询数据库,然后组织语言回复用户。太神奇了!
添加退换货处理
@Component
public class RefundService {
@Description("检查订单是否可以退换货")
public RefundEligibility checkRefundEligibility(@Description("订单号") String orderNumber) {
Order order = orderRepository.findByOrderNumber(orderNumber);
if (order == null) {
return new RefundEligibility(false, "订单不存在");
}
// 检查是否在退换货期限内
LocalDateTime orderTime = order.getCreateTime();
LocalDateTime now = LocalDateTime.now();
long days = ChronoUnit.DAYS.between(orderTime, now);
if (days > 7) {
return new RefundEligibility(false, "已超过7天退换货期限");
}
if (order.getStatus().equals("DELIVERED")) {
return new RefundEligibility(true, "可以申请退换货");
}
return new RefundEligibility(false, "订单状态不允许退换货");
}
@Description("创建退换货申请")
public String createRefundRequest(
@Description("订单号") String orderNumber,
@Description("退换货原因") String reason) {
// 创建退换货申请逻辑
RefundRequest request = new RefundRequest();
request.setOrderNumber(orderNumber);
request.setReason(reason);
request.setStatus("PENDING");
request.setCreateTime(LocalDateTime.now());
refundRepository.save(request);
return "退换货申请已提交,申请单号:" + request.getId() + ",我们会在24小时内处理。";
}
}
与其他框架的真实对比
经过实际使用,我也尝试了LangChain4j和Spring AI官方版本。让我客观地分享一下对比体验:
Spring AI Alibaba vs LangChain4j
LangChain4j的优势:
1. 文档和社区更成熟 LangChain4j的文档确实更详细,社区也更活跃。我遇到问题时,在GitHub和Stack Overflow上更容易找到答案。
2. 模型支持更广泛 支持的AI服务提供商更多,包括Azure OpenAI、Anthropic Claude、Google PaLM等。如果你需要用多种不同的模型,LangChain4j更有优势。
3. 工具链更完整 提供了更多开箱即用的工具,比如PDF解析、Web搜索、数据库查询等。
// LangChain4j的工具定义方式
@Tool("搜索网络信息")
public String searchWeb(String query) {
return webSearchService.search(query);
}
4. 灵活性更高 可以更细粒度地控制AI的行为,比如自定义token计算、重试策略等。
Spring AI Alibaba的优势:
1. Spring生态集成度 如果你的项目已经是Spring技术栈,集成成本几乎为零。所有的Spring特性(事务、缓存、安全、监控)都能无缝使用。
// 可以直接使用Spring的声明式事务
@Transactional
public String processUserQuery(String userId, String query) {
// AI处理 + 数据库操作都在同一个事务中
String response = chatClient.prompt().user(query).call().content();
userInteractionService.saveInteraction(userId, query, response);
return response;
}
2. 中文支持更好 阿里云的通义模型对中文的理解确实更准确,特别是在处理中文的语境、习惯用语方面。
3. 企业级特性 内置了很多企业需要的功能,比如API限流、监控埋点、错误处理等。
4. 部署和运维 如果你在用阿里云,部署和监控都更方便。而且有企业级支持。
性能对比(基于我的测试):
// 我做的简单性能测试
@Test
public void performanceTest() {
int requestCount = 100;
String testMessage = "请介绍一下你的功能";
// Spring AI Alibaba
long startTime = System.currentTimeMillis();
for (int i = 0; i < requestCount; i++) {
springAiService.chat(testMessage);
}
long springAiTime = System.currentTimeMillis() - startTime;
// LangChain4j
startTime = System.currentTimeMillis();
for (int i = 0; i < requestCount; i++) {
langChain4jService.chat(testMessage);
}
long langChainTime = System.currentTimeMillis() - startTime;
System.out.println("Spring AI Alibaba: " + springAiTime + "ms");
System.out.println("LangChain4j: " + langChainTime + "ms");
}
在我的测试中,两者性能差距不大,主要瓶颈都在网络调用上。
Spring AI Alibaba vs Spring AI 官方版
Spring AI 官方版的优势:
1. 官方维护 这是Spring官方项目,长期维护有保障,不用担心突然停止更新。
2. 国际化支持更好 如果需要支持多种语言和地区,官方版本的国际化做得更好。
3. 文档标准化 文档风格和Spring其他项目保持一致,学习体验更连贯。
Spring AI Alibaba的优势:
1. 本土化优势
- 网络访问更稳定(不用翻墙)
- 中文处理能力更强
- 符合国内的合规要求
2. 阿里云生态 如果你已经在使用阿里云的其他服务,集成度会更好。
3. 企业级功能 提供了更多企业需要的功能,比如多租户支持、详细的使用统计等。
实际项目中的最佳实践
经过几个月的使用,我总结了一些实用的经验:
1. 错误处理和降级策略
@Service
public class RobustChatService {
@Autowired
private ChatClient chatClient;
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public String chat(String message) {
try {
return chatClient.prompt()
.user(message)
.call()
.content();
} catch (Exception e) {
log.error("AI调用失败: ", e);
throw e;
}
}
@Recover
public String recover(Exception e, String message) {
// 降级处理:返回预设回复或转人工
return "抱歉,AI助手暂时不可用,已为您转接人工客服。";
}
}
2. 成本控制
AI调用是要花钱的,必须做好成本控制:
@Component
public class AIUsageController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String DAILY_USAGE_KEY = "ai_usage:daily:";
private static final int DAILY_LIMIT = 1000; // 每天1000次调用限制
public boolean canMakeRequest(String userId) {
String key = DAILY_USAGE_KEY + LocalDate.now().toString() + ":" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
// 第一次调用,设置过期时间
redisTemplate.expire(key, Duration.ofDays(1));
}
return count <= DAILY_LIMIT;
}
}
3. 监控和日志
@Component
@Slf4j
public class AIMetrics {
private final MeterRegistry meterRegistry;
private final Counter aiCallCounter;
private final Timer aiCallTimer;
public AIMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.aiCallCounter = Counter.builder("ai.calls.total")
.description("Total AI calls")
.register(meterRegistry);
this.aiCallTimer = Timer.builder("ai.calls.duration")
.description("AI call duration")
.register(meterRegistry);
}
public String monitoredChat(String message) {
return Timer.Sample.start(meterRegistry)
.stop(aiCallTimer.recordCallable(() -> {
aiCallCounter.increment();
return chatClient.prompt().user(message).call().content();
}));
}
}
4. 内容安全
@Service
public class ContentSafetyService {
public boolean isContentSafe(String content) {
// 集成阿里云内容安全服务
try {
ContentScanResponse response = contentScanClient.scanText(content);
return response.getResult().equals("PASS");
} catch (Exception e) {
log.error("内容安全检查失败", e);
// 安全起见,异常时认为不安全
return false;
}
}
public String safeChatResponse(String userMessage) {
if (!isContentSafe(userMessage)) {
return "您的消息包含不当内容,请重新输入。";
}
String response = chatClient.prompt().user(userMessage).call().content();
if (!isContentSafe(response)) {
return "抱歉,我无法回答这个问题。";
}
return response;
}
}
遇到的坑和解决方案
1. Token限制问题
最开始我没注意到token限制,对话历史越来越长,最后超出了模型的上下文窗口:
// 错误的做法:无限制保存历史
public String chat(String message) {
allHistory.add(new UserMessage(message)); // 这里会越来越大
return chatClient.prompt().messages(allHistory).call().content();
}
// 正确的做法:智能截断
public String chat(String message) {
history.add(new UserMessage(message));
// 简单截断:只保留最近的对话
if (history.size() > 20) {
history = history.subList(history.size() - 20, history.size());
}
return chatClient.prompt().messages(history).call().content();
}
2. 并发问题
多个用户同时聊天时,如果处理不当会互相影响:
// 错误的做法:共享状态
@Service
public class ChatService {
private List<Message> sharedHistory = new ArrayList<>(); // 危险!
}
// 正确的做法:按用户隔离
@Service
public class ChatService {
private final Map<String, List<Message>> userHistories = new ConcurrentHashMap<>();
public String chat(String userId, String message) {
List<Message> userHistory = userHistories.computeIfAbsent(userId, k -> new ArrayList<>());
// 后续处理...
}
}
3. 内存泄漏
对话历史如果只增不减,很容易造成内存泄漏:
@Scheduled(fixedRate = 3600000) // 每小时清理一次
public void cleanupOldConversations() {
LocalDateTime cutoff = LocalDateTime.now().minusHours(2);
userHistories.entrySet().removeIf(entry -> {
// 根据最后活跃时间判断是否清理
return getLastActiveTime(entry.getKey()).isBefore(cutoff);
});
}
总结:选择建议
经过几个月的实践,我的建议是:
选择Spring AI Alibaba的场景:
- 已有Spring Boot项目,需要快速集成AI功能
- 主要服务中文用户
- 使用阿里云基础设施
- 需要企业级支持和服务保障
- 团队对Spring生态比较熟悉
选择LangChain4j的场景:
- 需要支持多种AI模型和服务商
- 对灵活性要求较高
- 有复杂的AI工作流需求
- 团队有较强的自研能力
选择Spring AI官方版的场景:
- 国际化项目
- 长期维护考虑
- 需要与Spring其他项目深度集成
对于我们这种中小型项目,Spring AI Alibaba确实是一个很好的选择。它让我这个AI小白也能快速上手,而且生产环境运行也很稳定。当然,技术选型还是要根据具体业务需求来定,没有银弹。
希望我的这些经验能帮到正在选择Java AI框架的朋友们。如果你也在使用这些框架,欢迎分享你的经验!
