news 2026/9/20 3:29:31

Spring AI Alibaba实战:用Java快速构建通义千问AI应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI Alibaba实战:用Java快速构建通义千问AI应用

做后端这么多年,我有个特别明显的感受:Java圈子上手大模型应用的速度,始终比Python圈子慢半拍。不是Java不行,是那时候合适的落地框架太少。直到Spring AI正式进入Spring家族,阿里又在它上面开源了Spring AI Alibaba,这个局面才算真正被打破。如果你是一个习惯了Spring Boot写业务的老Java,想用通义千问这类大模型快速搭出AI接口,但又不想从零啃Python的请求封装、会话管理、流式输出,那Spring AI Alibaba就是为你准备的。

这篇教程我会从实际开发者的视角来写,不空谈概念。先把它和底层模型服务的关系捋清楚,然后带你把第一个对话接口跑通,接着用真实代码演示流式输出、多轮对话、函数调用和结构化输出,最后顺手解决几个我踩过的高频坑。整个教程读完,你应该能独立把一个基于通义千问的Java AI应用拆出来,还能知道进展到生产环境时要注意什么。

1. Spring AI Alibaba是什么,为什么值得上手

1.1 它和Spring AI、阿里云模型服务到底什么关系

很多第一次接触的人,会被三个名词绕晕:Spring AI、Spring AI Alibaba、DashScope(阿里云百炼)。我用一句大白话解释:Spring AI是规范,Spring AI Alibaba是阿里对这套规范的具体实现和增强,DashScope是底层真正跑模型的服务。

Spring AI不是阿里搞的,它是Spring生态官方的AI集成项目,目标是把大模型能力抽象成Spring风格API。你只要写ChatClient,对着接口调用,它帮你封装了剩下百分之七八十的工程逻辑,比如HTTP通信、消息组装、流式解析、Token统计。但它本身不绑定任何一家模型厂商。你既可以用OpenAI,也可以用通义千问,只需要换一个starter依赖和配置。

Spring AI Alibaba就是在这个抽象层之下做的国内落地方案。它把DashScope上的通义千问、通义万相、嵌入模型等接进了Spring AI的模型体系。换句话说,你过去用Spring AI的API,现在不用自己写一堆兼容层,直接用阿里提供的starter,内部自动转成DashScope的API请求。如果你所在的公司又要求部署在阿里云上,那就更省事,认证、网络、生态都顺理成章。

还有一层关系很重要:Spring AI Alibaba不只是一个对接DashScope的驱动。它还涉及模型网关、函数调用、向量数据库、Agent编排等扩展能力。虽然基础教程我们主要讲对话和工具调用,但你要知道这个项目的前景不只是聊天。

1.2 核心概念速览,花十分钟建立框架

在写代码前,我先带你过一遍会用到的核心概念。这些概念你会反复碰到,提前建立印象,后面看代码会快很多。

  • ChatClient:面向用户的主入口。类似于Spring Web里的RestTemplate,你用它发起Prompt,接收模型的回复。
  • ChatModel:底层模型调用的抽象接口。ChatClient内部会持有ChatModel,由starter自动装配。
  • Prompt:你发给模型的完整请求,包含用户消息、系统消息、模型参数选项。
  • Message:一条消息。常见的有UserMessage、SystemMessage、AssistantMessage。
  • Tool Calling(函数调用):允许模型在回复时携带一个结构化请求,调用你提前注册好的Java函数。
  • Structured Output(结构化输出):让模型按JSON Schema返回结果,并自动反序列化成Java对象。
  • Vector Store / Embedding:向量化模型与向量数据库相关,在RAG场景使用,基础教程先不展开。

我见过不少人一上来就去看Agent、RAG这类高级功能,结果基础Prompt都调不稳,最后全变成玄学调参。我的建议是先把握ChatClient、Message、Tool Calling这三件事,把对话链路跑通,再往上层走。

2. 环境准备:10分钟跑通第一个对话接口

2.1 版本、依赖和密钥准备

本机环境我用的是JDK 17、Maven 3.9、Spring Boot 3.2.5。Spring AI Alibaba目前对JDK 17的要求比较常见,如果你还在用JDK 8,建议先别挣扎,直接升级。做AI应用会大量处理字符串和JSON,新版JDK在性能、虚拟线程方面都有天然优势。

依赖引入有两种方式。第一种是直接引用Spring AI Alibaba自家的BOM,统一管理版本:

<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-bom</artifactId> <version>1.0.0-M2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

然后引入starter:

<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> </dependency>

第二种是直接用Spring Initializr生成项目时,在AI分类里选DashScope相关依赖。如果你不会配,就首选第一种,清晰直接。

接下来去阿里云百炼控制台开通DashScope服务,创建一个API Key。把API Key保存好,建议直接通过环境变量注入,不要硬编码进代码仓库。后面配置里我会用${DASHSCOPE_API_KEY}这种占位符。

注意:买模型前先确认企业账号是不是走专属资源池,如果走标准API,默认的通义千问qwen-plus对绝大多数场景够用,不一定要上最高规格的模型。

2.2 配置文件与最小Demo

application.yml里做基础配置:

spring: application: name: spring-ai-alibaba-demo ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.7

这里面的spring.ai.dashscope是Spring AI Alibaba的自动配置前缀。chat.options.model是默认对话模型,temperature控制答案随机性。如果你要更保守的结果,可以调低到0.2,如果做创意文案,再往0.8以上走。

接着建一个Controller,最简实现:

@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.builder() .defaultSystem("你是一个Java开发助手,回答尽量简洁、准确。") .build(); } @GetMapping("/chat") public String chat(String message) { return chatClient.prompt(message).call().content(); } }

启动项目,访问http://localhost:8080/chat?message=用一句话介绍Spring AI Alibaba,如果配置没问题,几秒钟内就能收到模型回复。走到这一步,你的第一个大模型接口就跑通了。

这里有个特别爽的点:你完全没手写HTTP请求、没拼接JSON、没处理鉴权头,全部由starter自动搞定。这背后就是Spring Boot的AutoConfiguration功劳。你写的代码只有业务部分,非常干净。

2.3 为什么用ChatClient而不是直接注入ChatModel

初学者常看到一个ChatModel的Bean,就想直接chatModel.call(prompt)。不是不行,但我不推荐。ChatModel属于底层API,你得自己处理Prompt选项、消息转换、结果提取,代码能写但很难维护。

ChatClient更像一个友好的门面对象,把常见的动作压缩成链式调用。你可以先不用理解它内部所有方法,把它当作一个"能发消息并能拿到回复的客户端”来看待。

我自己的习惯是,在Service层注入ChatClient,Controller层只负责参数校验和结果返回。这样后面如果要加多轮记忆、Tracing,不需要改Controller。

3. 核心功能实战:从单轮问答到流式输出

3.1 ChatClient的三种调用方式

ChatClient最基础的用法是调用.call(),它代表同步等待模型把整段回答返回。适合后端逻辑需要完整答案后再做处理的场景,比如内容提取、数据分析、生成摘要。

下面这段代码演示了带系统消息和参数的调用:

String result = chatClient.prompt() .system("你是专业客服,回答语气要礼貌。") .user("我有一个问题...") .temperature(0.5) .call() .content();

需要注意的是,temperature不仅是随机参数,也影响模型输出的稳定性。如果你在做一个规则性很强的任务,比如抽取JSON,建议把它调到贴近0。如果是头脑风暴,再调高。

第二种是.stream(),用于流式输出。模型每生成一段内容,后端就通过响应式流把你推给前端。聊天场景几乎必须用这个,用户不用等十几秒才有反应。

@GetMapping(value = "/chat/stream", produces = "text/plain; charset=UTF-8") public Flux<String> chatStream(String message) { return chatClient.prompt(message).stream().content(); }

启动后直接访问这个接口,在浏览器里你能看到文字像打字机一样一段段出现。前端如果是React,直接接一个EventSource,解析文本流很方便。

第三种是.chat()? 严格来说Spring AI早期版本有该方法,新版本已经统一收敛到call()stream()。如果你在网上看到老代码,建议直接按你当前依赖版本来。API改动是Java AI框架的常态,依赖版本不同很多方法都不一样,遇到编译错误先查版本。

3.2 流式输出背后的原理,以及前端配合方式

很多人用流式接口时会遇到一个问题:返回的数据不是标准JSON,而是每行一段文本。这不是Bug,是SSE(Server-Sent Events)的典型格式。

我举个例子,前端用axios直接请求,可能需要加几行代码处理text/event-stream格式。你如果用的是服务端渲染的Thymeleaf或WebSocket,也可以把Flux<String>通过WebSocket转发,体验更好。

更简单的方法是用SseEmitter:

@GetMapping("/chat/sse") public SseEmitter chatSse(String message) { SseEmitter emitter = new SseEmitter(0L); Flux<String> content = chatClient.prompt(message).stream().content(); content.subscribe( data -> emitter.send(data), emitter::completeWithError, emitter::complete ); return emitter; }

如果你只想快速做原型,直接用.stream()text/event-stream就足够。我的经验是:生产项目不要在前端绕太多层,最好的方案是后端做SSE网关,前端只管收消息,后端的模型切换、负载、鉴权全部挡在网关后面。

3.3 多轮对话:别傻傻手工拼接历史

模型本身没有记忆。你每发出一轮请求,它看到的就是你这次发过去的内容。要实现"多轮对话",核心思路是把历史消息一并发给模型。

最原始的方式是手动维护一个List:

List<Message> messages = new ArrayList<>(); messages.add(new SystemMessage("你是一个AI助手")); messages.add(new UserMessage("第一句")); messages.add(new AssistantMessage("第一句回复")); messages.add(new UserMessage("第二句"));

把全部消息塞进Prompt,丢给模型。这种方式在小Demo里能跑,但一旦用户多了,消息管理、Token超限、上下文裁剪全成问题。

Spring AI里面提供了ChatMemoryAdvisor机制,你可以把它理解成给ChatClient装上一块"记忆插件"。最简单的方式是使用MessageChatMemoryAdvisor,它会把会话历史自动附加到下一次请求中,同时不会让你的业务代码里出现一堆List。

ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) .build();

然后你在请求时指定会话ID:

String answer = chatClient.prompt() .user("你还记得我之前问你什么吗?") .advisors(a -> a.param(ChatMemoryAdvisor.CONVERSATION_ID, "session-001")) .call() .content();

这样同一个会话ID的消息会自动串联起来。要注意的是内存版InMemoryChatMemory只适合开发环境,生产环境建议换成Redis实现,否则实例重启对话就没了,而且多实例部署时会话会分散。

4. 让模型真正干活:Function Calling与结构化输出

4.1 函数调用:从“聊天玩具”走向业务系统

聊到第四部分,我觉得是整个教程的分水岭。纯聊天好写,难的是让AI真正触达你的业务数据。比如用户问"北京现在热吗",如果你只让模型自由发挥,它可能会瞎编温度。正确做法是让模型识别用户意图,然后调用你提供的一个天气函数,拿到真实数据后再组织回答。

这个机制在Spring AI里叫Tool Calling,在阿里这套实现里同样支持。我使用的方式是注册一个Function类型的Bean,再用@Description告诉模型这个函数是干什么用的。

@Bean @Description("根据城市名获取当前天气,参数格式: {\"city\": \"北京\"}") public Function<WeatherRequest, WeatherResponse> currentWeather() { return new WeatherFunction(); }

WeatherFunction内部你直接调用气象服务API,返回一个结构清晰的对象:

public class WeatherFunction implements Function<WeatherRequest, WeatherResponse> { @Override public WeatherResponse apply(WeatherRequest request) { // 调用真实天气接口,这里省略实现 return new WeatherResponse(request.city(), 28, "晴"); } }

在调用ChatClient时,通过.functions("currentWeather")把这个工具挂上去:

String answer = chatClient.prompt() .system("你是天气助手,回答用户问题前先调用工具。") .user("北京现在热吗?") .functions("currentWeather") .call() .content();

模型看到"北京"这个实体时,会认为需要调用currentWeather,于是返回一个特殊消息。Spring AI框架会自动帮你执行这个Java函数,再把结果回传给模型,最后由模型生成自然语言回答。

整个过程对业务代码是透明的,你只需要保证:

  1. 函数描述足够清楚,尤其写清楚参数结构。
  2. 函数返回结果不要带无关字段,模型会依据返回内容生成回答。
  3. 不要暴露任何敏感数据和危险操作函数,AI调用的入口和用户输入是同一路径,要当接口对待。

我踩过的坑是:参数名用了中文或命名不够直观,模型经常生成不了正确的JSON。后来我统一用英文参数名,并在@Description里给一个示例JSON,效果立刻好了很多。这是工具调用最实用的小技巧。

4.2 结构化输出:让模型返回可以直接入库的JSON

传统开发中,我们通常需要从模型回答里提取实体、情感、分类标签。如果靠正则去解析大段文本,很容易碎。Spring AI提供了结构化输出,你可以直接让模型返回Vo对象。

比如我有一个用户信息类:

public record UserInfo(String name, Integer age, String city) {}

然后调用:

UserInfo user = chatClient.prompt() .user("从这段话里提取用户信息:我叫张伟,今年28岁,家在杭州。") .call() .entity(UserInfo.class);

最终user.name()是"张伟",user.age()是28,user.city()是"杭州"。它会自动完成从自然语言到Java对象的映射,不需要你手写JSON解析。

这套能力背后的逻辑是:框架根据你传入的Java类型自动生成JSON Schema,模型按这个Schema输出,框架再把它反序列化。所以你需要注意:

  • 业务字段尽量用基本类型,避免复杂泛型嵌套。
  • 如果字段允许为空,建议使用包装类型或Optional,防止反序列化NPE。
  • 模型输出偶尔会不遵循Schema,生产代码里要做异常兜底,把失败的回答降级为人工处理。

还有一点非常实用:你可以用ParameterizedTypeReference处理列表结构,比如一批新闻标题的提取。这个API我用过几次,在处理Excel导入、信息清洗场景时特别能提效。

5. 生产环境避坑指南

5.1 常见问题排查表

我整理了一张速查表,都是平时聊过最多的几类问题:

现象大概率原因解决办法
401 UnauthorizedAPI Key无效或没走环境变量检查DASHSCOPE_API_KEY,重启应用确认配置生效
404 model not exists当前账号未开通对应模型去百炼控制台开通模型权限,或换用qwen-plus等默认模型
接口响应很慢同步等待完整输出改用流式接口,必要时调低maxTokens
偶发超时模型队列繁忙或网络抖动增加重试机制,设置合理超时时间
多轮对话串上下文未用会话ID隔离使用ChatMemoryAdvisor并传唯一会话ID
返回JSON格式错乱温度太高或Schema约束不足temperature调低,使用结构化输出
生产环境内存暴涨上下文无限累积给会话历史设置窗口上限,定期清理

排查的时候有个技巧:先确认是不是网络层问题,再确认是不是配置问题,最后才怀疑模型问题。不要一上来就调temperature,那只会让结果更玄学。

5.2 模型选型与量化建议

聊天原型阶段,用qwen-plus是最省心的,速度快、效果均衡。如果你的任务偏专业要求高,可以试试qwen-max,但成本会上升。如果做短文本分类、向量化、标题生成,也可以考虑qwen-turbo,延迟低,便宜很多。

我建议在应用里把模型名做成配置项,不要写死在代码。通过spring.ai.dashscope.chat.options.model去配,这样上测试环境用turbo,生产环境用max,一个配置就能切换。

对于嵌入模型,RAG场景里我会用text-embedding-v3,维度适中,效果还不错。不过如果你已经开始做RAG,建议同时选好向量数据库。Spring AI Alibaba生态里对Milvus、PostgreSQL等都有对接,但我一般小白阶段只推荐先用本地SimpleVectorStore把流程跑通再说。

5.3 Spring AI Alibaba Admin:像运维一样管理AI应用

聊到热词Spring AI Alibaba Admin,有人可能以为有个独立后台管理系统。其实在官方生态里,admin更多强调的是一套可观测和可管控能力。你不需要一上来就搭一个复杂的运营后台,但至少要从应用侧把模型调用看住。

我自己的做法分三层:

第一层,暴露Actuator端点,监控应用存活和基础指标。只要引入:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

然后配置management.endpoints.web.exposure.include=*,Spring AI Alibaba的自动配置会带上模型调用相关的指标,Prometheus可以直接抓。

第二层,在代码里统一记录Token消耗。你拿到ChatResponse后,从response.getMetadata().getUsage()拿到输入Token、输出Token,入库。时间长了,你就知道哪个部门、哪个应用在烧钱。这部分我通常会做成一个切面,不侵入业务代码。

第三层,做一个轻量Admin接口。真正到团队协作时,你需要一套页面去维护模型路由和API Key,这就是我理解的Spring AI Alibaba Admin实践方向。不需要很强,能支持以下功能就行:

  • 查看每个应用的调用次数、Token消耗和失败率。
  • 在线修改指定业务的模型名和temperature。
  • 建立团队维度的预算告警。

根据我的经验,一个小团队自己写个几十行代码的管理接口完全够用。重点不是管理界面做得多花哨,而是要有数据,有告警,出了问题能快速定位。

5.4 把基础教程变成生产落地的心得

最后分享一点我的判断。Spring AI Alibaba目前的迭代节奏相当快,API还在不断演进。你在网上搜到的代码可能一个月后就过时,所以学习它的核心不是背API,而是抓住三层思维模型:

  • 第一层:用ChatClient和Prompt操作模型,替代自己写HTTP接口。
  • 第二层:用Function Calling把AI接进现有业务系统,让模型学会调用你的能力。
  • 第三层:用Token监控、模型配置和异常兜底,把AI应用当作正式业务系统来运营。

我个人实际做项目时,会在项目里写一个AiAssistantService,把所有ChatClient调用集中在一个类,不散落各处。这样以后要从qwen-plus换到别的模型,或者加上RAG、Agent,只需要在这一个类上做扩展,其他代码不动。

如果你正要上手,建议先别追求高大上的Agent框架,老老实实把今天讲的几个点都练一遍。你会发现,从“调通接口”到“能落地到业务系统”,其实就差一次函数调用和结构化输出的门槛。迈过去,路就顺了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 3:27:04

Android Studio中文界面设置指南:官方语言包安装与避坑全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 3:26:34

vLLM多卡分布式推理部署实战:从张量并行到显存优化全指南

1. 从单卡爆显存说起&#xff1a;为什么需要分布式推理搞大模型部署的朋友应该都经历过这样一个瞬间&#xff1a;模型加载到一半&#xff0c;屏幕上赫然出现一行显存不足的报错&#xff0c;或者OOM直接把进程杀了。明明自己的显卡已经是旗舰级别&#xff0c;却连一个大参数的模…

作者头像 李华
网站建设 2026/9/20 3:20:47

如何导出微信聊天记录永久保存:WeChatMsg 免费上手指南

如何导出微信聊天记录永久保存&#xff1a;WeChatMsg 免费上手指南 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeCh…

作者头像 李华
网站建设 2026/9/20 3:17:23

MBA写作降AI率实测:8类工具原理、效果与避坑指南

MBA课程作业最让人头疼的事之一&#xff0c;就是案例分析报告写完之后&#xff0c;打开学校系统里的AI检测一跑&#xff0c;显示“疑似AI生成比例&#xff1a;42%”。更离谱的是&#xff0c;有些段落明明是自己一个字一个字敲的&#xff0c;也被标红。于是大家开始到处找所谓的…

作者头像 李华