ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring AI Alibaba:Java开发者的大模型工程化实践指南

Spring AI Alibaba:Java开发者的大模型工程化实践指南 1. 从“AI应用”到“AI工程化”为什么我们需要 Spring AI Alibaba最近两年大模型和AI应用开发的热度居高不下。作为一名Java后端开发者我最初接触AI的方式和很多人一样调用OpenAI的API或者用一些开源的Python库。写个简单的HTTP客户端把用户的问题包装成Prompt然后解析返回的JSON。刚开始觉得挺酷但项目稍微复杂一点问题就来了。比如我需要接入多个模型供应商OpenAI、通义千问、智谱AI每个供应商的API签名、参数格式、错误码都不一样我得为每个供应商写一套适配代码。再比如我想实现一个带历史记忆的聊天功能或者一个能调用外部工具如查询天气、搜索数据库的智能体Agent这些逻辑很快就会和我的业务代码搅在一起变得难以维护。更别提流式响应、函数调用Function Calling、RAG检索增强生成这些更高级的特性了自己从零实现不仅耗时而且容易出错。这让我意识到我们正处在一个从“写AI应用”到“做AI工程化”的转折点。AI能力正在成为一种基础设施就像数据库、缓存、消息队列一样。我们需要一个框架来统一管理这些AI能力让开发者能像使用JdbcTemplate操作数据库一样用声明式、标准化的方式去使用大模型。这就是Spring AI Alibaba出现的背景。简单来说Spring AI Alibaba是Spring官方AI项目在阿里巴巴生态下的实现与增强。它基于Spring AI的核心抽象提供了对阿里云灵积平台DashScope上百款模型如通义千问、通义视觉等的一站式集成同时深度整合了Spring Boot的自动配置、依赖注入等特性。它的目标是让Java开发者能以最“Spring”的方式最低成本地将大模型能力引入到自己的应用中。如果你熟悉Spring生态可以把它理解为AI领域的“Spring Data JPA”。JPA定义了一套操作数据库的接口规范而Hibernate、EclipseLink是它的实现。Spring AI定义了一套操作大模型的接口如ChatClientEmbeddingClient而Spring AI Alibaba就是针对阿里云模型服务的“实现提供商”。这意味着你学到的Spring AI Alibaba的知识其核心抽象是通用的未来切换到底层模型或供应商代码改动可以非常小。2. Spring AI Alibaba 的核心架构与核心抽象要理解一个框架首先要看它定义了哪些核心接口以及这些接口是如何组织在一起的。Spring AI Alibaba的架构清晰体现了“面向接口编程”和“依赖倒置”的原则。2.1 核心模块与依赖关系一个典型的Spring AI Alibaba应用其核心依赖通常如下以Maven为例dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-spring-boot-starter/artifactId version${spring-ai-alibaba.version}/version /dependency这个Starter会引入一系列传递依赖其中最关键的是spring-ai-core。这说明了Spring AI Alibaba与Spring AI的关系前者是后者的一个具体实现扩展。这种分层设计的好处是你的业务代码依赖于spring-ai-core中定义的稳定抽象而不是某个具体供应商的SDK。2.2 两大核心客户端ChatClient 与 EmbeddingClient框架的核心抽象主要围绕两个客户端展开它们是你与模型交互的主要入口。1. ChatClient对话与补全这是最常用、最核心的接口。顾名思义它负责处理聊天、文本补全等生成式任务。无论你是想实现一个智能客服还是一个代码助手最终都会通过这个接口来调用模型。它的核心方法很简单ChatResponse call(ChatRequest request)。你构建一个包含消息列表Message和参数如温度、最大生成长度的请求对象交给它它返回一个包含模型生成结果的响应对象。框架会帮你处理HTTP通信、认证、序列化/反序列化、错误处理等所有底层细节。在Spring AI Alibaba中这个接口的默认实现会去调用阿里云灵积平台对应的聊天模型API比如qwen-max或qwen-plus。2. EmbeddingClient文本向量化这是实现RAG、语义搜索等高级应用的基础。它的作用是将一段文本或代码、图片转换成一个高维度的数值向量即Embedding。这个向量就像文本的“数学指纹”语义相近的文本其向量在空间中的距离也更近。它的核心方法是ListDouble embed(String text)或EmbeddingResponse embed(EmbeddingRequest request)。你传入文本它返回向量。这个向量可以被存入向量数据库如Milvus, Elasticsearch with vector plugin后续的检索就是计算查询文本的向量与库中向量之间的相似度。在阿里云灵积平台有专门的文本嵌入模型如text-embedding-v2来提供这项服务Spring AI Alibaba的EmbeddingClient实现就是对它的封装。2.3 消息Message与提示词模板PromptTemplate模型接收的输入不是简单的字符串而是结构化的消息Message。Spring AI定义了多种消息类型最常用的是SystemMessage系统消息用于设定AI的角色、背景和行为规范。例如“你是一个专业的Java编程助手回答要简洁准确。”UserMessage用户消息即用户输入的问题或指令。AssistantMessage助手消息通常是模型上一轮的回答用于在多轮对话中提供历史上下文。一个典型的对话请求就是由这些消息对象组成的列表。框架提供了便捷的Prompt类来封装这个消息列表。直接拼接字符串来构造消息很容易出错且不灵活。因此Spring AI提供了PromptTemplate。它类似于Spring MVC中的视图模板如Thymeleaf允许你定义带有占位符的模板字符串然后在运行时动态注入变量。例如你可以定义一个模板“请用{language}语言写一个函数实现{function}功能。”。在代码中通过PromptTemplate.create(template).render(Map.of(“language”, “Python”, “function”, “快速排序”))就能生成最终的Prompt。这对于构建需要动态内容的AI应用至关重要。2.4 自动配置与属性绑定这是Spring Boot的精华所在。你几乎不需要写任何样板化的配置代码。只需要在application.yml或application.properties中配置必要的连接信息Spring AI Alibaba的自动配置类就会帮你创建好ChatClient和EmbeddingClient的Bean。一个最基础的配置示例如下spring: ai: alibaba: chat: # 阿里云灵积平台API密钥 api-key: sk-你的api-key # 指定使用的模型如通义千问Max options: model: qwen-max temperature: 0.7 max-tokens: 2000配置完成后你就可以在任何需要的地方通过Autowired直接注入ChatClient来使用了。这种“约定大于配置”的方式极大地简化了集成流程。3. 快速开始构建你的第一个AI对话应用理论说了这么多我们来动手写一个最简单的、能跑起来的例子。这个例子将展示从零开始如何使用Spring AI Alibaba创建一个Spring Boot应用并实现一个单轮对话接口。3.1 环境准备与项目初始化首先你需要准备以下几样东西Java开发环境JDK 17或更高版本Spring AI 要求 JDK 17。构建工具Maven 3.6 或 Gradle。IDEIntelliJ IDEA推荐或 Eclipse。阿里云账户与API Key访问阿里云官网开通灵积平台DashScope服务并在控制台创建API Key。这是调用模型的凭证。接下来我们用Spring Initializrstart.spring.io快速生成项目骨架。Project: MavenLanguage: JavaSpring Boot: 选择最新的稳定版如3.2.xDependencies: 添加Spring Web和Spring AI Alibaba。在Initializr的“Add Dependencies”搜索框中输入“Alibaba AI”应该能找到对应的Starter。如果找不到你也可以手动添加我们前面提到的Maven依赖。点击生成并下载项目用IDE打开。3.2 编写核心配置与控制器第一步配置API Key和模型在src/main/resources/application.yml文件中添加你的配置spring: application: name: first-spring-ai-app ai: alibaba: chat: api-key: ${ALIBABA_AI_API_KEY:sk-your-test-key-here} # 建议使用环境变量避免密钥硬编码 options: model: qwen-max # 使用通义千问Max模型 temperature: 0.8 # 创造性0-1越高越随机 max-tokens: 1024 # 生成的最大token数 server: port: 8080重要提示永远不要将真实的API Key提交到代码仓库如Git。这里使用${ALIBABA_AI_API_KEY:}的写法意味着会优先从系统环境变量ALIBABA_AI_API_KEY中读取如果找不到则使用冒号后的默认值。在实际生产环境中你应该通过环境变量、配置中心如Nacos或密钥管理服务来注入密钥。第二步创建一个简单的REST控制器我们在src/main/java/com/example/demo目录下创建一个ChatController.java。package com.example.demo; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Map; RestController public class ChatController { // 核心注入由Spring AI Alibaba自动配置的ChatClient Autowired private ChatClient chatClient; /** * 最简单的对话接口 * param message 用户输入 * return AI的回答 */ GetMapping(/chat/simple) public String chatSimple(RequestParam(value msg, defaultValue 你好介绍一下你自己) String message) { // 1. 构建一个只包含用户消息的Prompt Prompt prompt new Prompt(message); // 2. 调用ChatClient String response chatClient.call(prompt).getResult().getOutput().getContent(); // 3. 返回结果 return response; } /** * 使用PromptTemplate的对话接口 * param topic 话题 * param style 风格 * return AI的回答 */ GetMapping(/chat/template) public String chatWithTemplate(RequestParam String topic, RequestParam(defaultValue 幽默) String style) { // 1. 定义模板字符串 String templateStr “请你以{style}的风格写一段关于{topic}的简短介绍。”; // 2. 创建PromptTemplate并渲染变量 PromptTemplate promptTemplate new PromptTemplate(templateStr); Prompt prompt promptTemplate.create(Map.of(“style”, style, “topic”, topic)); // 3. 调用并返回 return chatClient.call(prompt).getResult().getOutput().getContent(); } }这段代码做了两件事定义了一个/chat/simple接口接收一个msg参数直接将其作为用户消息发送给模型。定义了一个/chat/template接口它使用了PromptTemplate。你可以传入topic如“Spring框架”和style如“严谨”模板会动态生成最终的Prompt。3.3 运行与测试启动你的Spring Boot应用运行DemoApplication里的main方法。控制台没有报错且看到Tomcat started on port 8080的日志说明启动成功。现在打开浏览器或使用Postman、curl进行测试测试简单接口GET http://localhost:8080/chat/simple?msgJava中的Spring框架是做什么的你应该会收到一段来自通义千问模型的、关于Spring框架的介绍。测试模板接口GET http://localhost:8080/chat/template?topic人工智能style风趣你应该会收到一段以风趣风格介绍人工智能的文字。恭喜你的第一个基于Spring AI Alibaba的AI应用已经成功运行起来了。整个过程你没有手动创建任何HTTP客户端没有处理JSON解析没有管理连接池只是通过注入一个ChatClient并调用其call方法就完成了与百亿参数大模型的交互。这就是框架带来的生产力提升。4. 超越基础对话探索框架的高级特性与设计模式一个只会单轮对话的应用价值有限。Spring AI Alibaba的强大之处在于它提供了一套完整的抽象来支持更复杂的AI应用模式。让我们看看如何利用这些特性。4.1 实现多轮对话与上下文管理真实的对话是有状态的。AI需要记住之前说过什么才能进行连贯的交流。在Spring AI中管理对话状态的核心是ChatMemory。ChatMemory是一个接口它定义了如何存储和检索历史消息。框架提供了几种实现最常用的是InMemoryChatMemory它将对话历史简单地保存在内存中。对于生产环境你可能需要基于Redis或数据库实现持久化的ChatMemory。下面是一个增强版的控制器演示了如何利用ChatMemory实现带上下文的对话package com.example.demo; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.ChatResponse; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(“/chat/context”) public class ChatContextController { private final ChatClient chatClient; private final InMemoryChatMemory chatMemory; // 通过构造器注入 public ChatContextController(ChatClient chatClient) { this.chatClient chatClient; this.chatMemory new InMemoryChatMemory(); // 为简单演示每个控制器实例一个内存存储 // 在实际应用中ChatMemory应该作为Bean被注入并且与用户会话绑定如用Session ID } PostMapping(“/session/{sessionId}”) public String chatWithContext(PathVariable String sessionId, RequestParam String message) { // 1. 获取或创建该会话的历史记录 ListMessage history chatMemory.get(sessionId); // 2. 构建本次请求的消息列表历史消息 新的用户消息 ListMessage messages new ArrayList(history); messages.add(new UserMessage(message)); // 3. 创建Prompt并调用 Prompt prompt new Prompt(messages); ChatResponse response chatClient.call(prompt); AssistantMessage assistantMessage response.getResult().getOutput(); // 4. 将本轮的用户消息和AI回复都存入历史 messages.add(assistantMessage); chatMemory.put(sessionId, messages); // 5. 返回本次AI的回复 return assistantMessage.getContent(); } DeleteMapping(“/session/{sessionId}”) public String clearContext(PathVariable String sessionId) { chatMemory.clear(sessionId); return “会话 ” sessionId “ 的历史记录已清空”; } }这个例子中我们为每个sessionId可以代表一个用户或一个对话线程维护独立的对话历史。每次对话都会带上全部历史从而实现上下文连贯。/clear接口用于清空历史开始新一轮对话。实操心得InMemoryChatMemory不适合生产环境因为应用重启后数据会丢失且无法在集群环境下共享。在实际项目中你需要实现一个基于Redis的ChatMemory将消息列表序列化后存储并以用户ID或会话ID作为Key。同时需要注意历史消息的长度模型通常有Token数量限制过长的历史需要采用滑动窗口或总结摘要的策略进行裁剪。4.2 使用函数调用Function Calling扩展AI能力大模型本身的知识是静态的且无法执行具体操作如查询数据库、调用外部API。函数调用也称为Tool Calling让AI具备了“动手能力”。AI可以根据用户的需求决定调用哪个预定义好的函数并将函数的执行结果作为上下文继续生成回答。Spring AI通过Function Calling框架来支持这一特性。你需要定义一个或多个Bean类型是FunctionCallback来描述你的函数名称、描述、参数模式。在调用ChatClient时将这些FunctionCallback注册到Prompt中。下面是一个模拟“查询天气”函数的例子package com.example.demo; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; import java.util.Map; Service class WeatherService { public String getCurrentWeather(String location, String unit) { // 这里应该是调用真实天气API的逻辑我们模拟返回 MapString, String mockData Map.of( “北京”, “晴朗25摄氏度”, “上海”, “多云22摄氏度”, “广州”, “阵雨28摄氏度” ); return mockData.getOrDefault(location, “未找到” location “的天气信息”) “单位” unit; } } Configuration class FunctionConfig { Bean public FunctionCallback weatherFunctionCallback(WeatherService weatherService) { // 使用FunctionCallbackWrapper便捷地包装一个Java方法 return FunctionCallbackWrapper.builder(weatherService) .withName(“getCurrentWeather”) // 函数名AI通过这个名字识别 .withDescription(“根据城市名称查询当前天气”) // 函数描述AI通过描述理解何时调用 .withInputType(Map.class) // 输入类型通常用Map或自定义DTO .withExecutor((inputMap) - { // 从AI传来的参数Map中提取参数 String location (String) inputMap.get(“location”); String unit (String) inputMap.getOrDefault(“unit”, “摄氏度”); // 执行真正的业务方法 return weatherService.getCurrentWeather(location, unit); }) .build(); } }在控制器中你需要将这个函数回调注册到Prompt中Autowired private FunctionCallback weatherFunctionCallback; GetMapping(“/chat/with-function”) public String chatWithFunction(RequestParam String question) { Prompt prompt new Prompt(question); // 关键将FunctionCallback设置到Prompt的选项中 prompt.getOptions().setFunctionCallbacks(List.of(weatherFunctionCallback)); ChatResponse response chatClient.call(prompt); return response.getResult().getOutput().getContent(); }当你询问“北京今天天气怎么样”时AI会识别出需要调用getCurrentWeather函数并自动构造出参数{“location”: “北京” “unit”: “摄氏度”}。框架会拦截这个调用请求执行我们定义的Executor获取真实的天气结果这里是模拟数据然后将这个结果作为新的上下文信息再次发送给AI让AI生成最终的回答“北京今天天气晴朗25摄氏度。”这个过程完全自动化你的业务代码只需要关心如何实现具体的函数逻辑。这是构建智能体Agent应用的基础。4.3 流式响应Streaming与性能优化默认的chatClient.call()是同步阻塞的它会等待模型生成全部内容后才返回。对于生成长文本的场景用户需要等待很长时间才能看到第一个字体验很差。流式响应Server-Sent Events, SSE允许我们将模型生成的内容以数据流的形式逐词或逐句地推送给前端。Spring AI Alibaba的ChatClient同样支持流式调用。它返回的不是ChatResponse而是一个FluxChatResponse响应式编程模型或可以通过InputStream处理。下面是一个使用Spring MVC返回SSE的示例GetMapping(value “/chat/stream”, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(RequestParam String message) { SseEmitter emitter new SseEmitter(60000L); // 超时时间60秒 Prompt prompt new Prompt(message); // 调用流式API chatClient.stream(prompt) .doOnNext(chunk - { try { // 每次收到一个数据块可能是一个词或一段话就发送给前端 String content chunk.getResult().getOutput().getContent(); if (content ! null) { emitter.send(SseEmitter.event().data(content)); } } catch (IOException e) { emitter.completeWithError(e); } }) .doOnComplete(emitter::complete) // 流结束时完成SSE连接 .doOnError(emitter::completeWithError) // 出错时结束 .subscribe(); // 开始订阅流 return emitter; }前端只需要创建一个EventSource对象连接到这个接口就可以实时接收到AI生成的内容。这极大地提升了交互体验也是目前主流AI应用的标配。性能优化提示流式响应虽然体验好但会占用更长的连接时间。在高并发场景下需要注意服务器的连接数限制和资源消耗。另外模型的“思考时间”Time to First Token是影响流式体验的关键选择响应速度更快的模型或调整参数如降低temperature可能有助于改善。5. 生产环境考量配置、监控与最佳实践将AI应用从Demo推向生产会面临一系列新的挑战。Spring AI Alibaba与Spring生态的无缝集成在这里提供了很多便利。5.1 多环境配置与密钥管理硬编码API Key是安全大忌。标准的做法是开发/测试环境使用application-dev.yml密钥可以放在本地但不要提交或使用本地的配置中心。生产环境通过环境变量注入或使用阿里云KMS密钥管理服务、Spring Cloud Alibaba Nacos配置中心来动态获取密钥。# application-prod.yml spring: ai: alibaba: chat: api-key: ${ALIBABA_CLOUD_API_KEY} # 从云平台或容器环境变量获取 options: model: qwen-max在Kubernetes部署中可以将API Key创建为Secret对象然后通过环境变量挂载到Pod中。5.2 连接池、超时与重试调用外部API网络是不可靠的。必须配置合理的超时和重试策略。Spring AI Alibaba的底层HTTP客户端通常支持这些配置但需要你在配置文件中显式设置。spring: ai: alibaba: chat: api-key: ${API_KEY} options: model: qwen-max # 连接池和客户端配置具体属性名需参考官方文档 client: connect-timeout: 5000ms # 连接超时 read-timeout: 30000ms # 读取超时生成长文本需要更长时间 max-connections: 100 # 最大连接数 max-connections-per-route: 50 # 每个路由最大连接数此外你可以利用Spring Retry库为ChatClient的调用添加重试逻辑例如在遇到网络抖动或服务端限流返回429状态码时自动重试。5.3 日志、监控与可观测性监控是生产系统的眼睛。你需要知道调用量每天/每分钟调用模型的次数。延迟每次请求的响应时间P50, P95, P99。Token消耗输入和输出各消耗了多少Token这直接关系到成本。错误率调用失败的比例。Spring Boot Actuator与Micrometer可以很好地集成。你可以为ChatClient的调用添加自定义的MicrometerTimer和Counter将指标暴露给Prometheus再通过Grafana展示。同时确保记录详细的日志。你可以通过配置logging.level.com.alibaba.cloud.ai为DEBUG来查看框架发起的详细HTTP请求和响应注意日志中可能会包含API Key和完整消息生产环境需谨慎开启或进行脱敏处理。5.4 成本控制与限流大模型API是按Token收费的。无限制的调用可能导致巨额账单。必须在应用层面实施限流和预算控制。限流使用Spring Cloud Gateway、Sentinel或Resilience4j为AI接口配置QPS限流。预算与告警在调用ChatClient前后记录本次请求消耗的Token数响应元数据中通常包含。累计每日/每月消耗并设置阈值告警。对于多租户系统需要为每个租户设置独立的Token预算。缓存对于一些重复性的、结果确定的查询例如“将‘你好’翻译成英文”可以考虑将AI的回复缓存起来使用Spring Cache集成Redis下次同样的问题直接返回缓存结果能显著节省成本和提升响应速度。5.5 与现有微服务架构集成Spring AI Alibaba天生与Spring Cloud Alibaba兼容。你可以在一个标准的Spring Cloud微服务中引入它。服务发现虽然模型API是外部服务但你的AI应用本身可以作为微服务注册到Nacos。配置管理将AI模型的配置如API端点、模型类型放在Nacos配置中心实现动态更新。流量防护使用Sentinel对AI服务调用进行熔断降级。当模型服务不稳定或响应过慢时快速失败并返回兜底内容如“服务繁忙请稍后再试”避免线程池被拖垮。链路追踪通过SkyWalking或Zipkin将一次用户请求从网关到业务服务再到AI模型调用的完整链路串联起来便于排查问题。将Spring AI Alibaba视为一个特殊的“数据源”或“外部服务客户端”利用Spring Cloud生态已有的强大工具来管理它是确保生产环境稳定性的关键。从我自己的实践来看Spring AI Alibaba最大的价值在于它统一了AI能力的使用范式。它让团队里的Java开发者不需要再去学习五花八门的模型API而是聚焦于业务逻辑和Prompt工程。它可能不是性能极致优化的那个毕竟多了一层抽象但在开发效率、代码可维护性和团队协作上带来的收益远超那一点微小的性能损耗。对于大多数企业级应用来说稳定、可维护、易集成远比极致的单次调用延迟更重要。这个框架正是朝着这个目标迈出的坚实一步。
返回列表