之前对接阿里云百炼平台一直都是用SpringAI的OpenAI模块,不过由于阿里云百炼与OpenAI之间不是完全兼容,所以还存在许多问题。
为了更好的与阿里云百炼平台对接,同时又能兼容SpringAI,阿里巴巴就在SpringAI的基础上推出了自己的集成API,Spring AI Alibaba。
1.快速入门
1.1.创建工程
首先创建一个SpringBoot工程:
选择Spring Web依赖,另一个是AI依赖。由于SpringAI默认不支持alibaba的百炼,所以我们先勾选OpenAI依赖,等会再修改。
1.2.引入依赖
接下里用alibaba的依赖取代OpenAI:
<!--注释或删除OpenAI依赖-->
<!--<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>-->
<!--引入Alibaba的AI依赖-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M6.1</version>
</dependency>
<!--引入WebFlux依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>1.3.配置文件
理论上说,我们同样需要配置模型的关键信息:
- API_KEY
- BASE_URL
- 模型名称和参数等
由于SpringAI Alibaba默认已经设定好了url路径,所以BASE_URL就i可以省略了。 修改application.yml配置,内容如下:
spring:
application:
name: Spring-AI-Alibaba
ai:
dashscope: # 这里的dashscope就是阿里云百炼的默认接口规范
api-key: ${ALIBABA_API_KEY} # 同样通过环境变量来设置
chat:
options:
model: deepseek-r1
logging:
level:
com.example: debug
org.springframework.ai: debug1.4.配置ChatClient
在config包下新建一个CommonConfiguration类:
package com.example.config;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CommonConfiguration {
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(DashScopeChatModel chatModel,ChatMemory chatMemory){
return ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor(),
new MessageChatMemoryAdvisor(chatMemory)
).build();
}
}阿里云百炼提供的对话模型是
DashScopeChatModel,所以配置ChatClient的时候也是注入这个。
1.5.对话接口
接下来,定义对话接口,在contorller包下新建ChatController类:
package com.example.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(
@RequestParam(value = "prompt",defaultValue = "你好")String prompt){
return chatClient.prompt()
.user(prompt)
.stream()
.content();
}
}1.6.测试

2.推理模型
具备深度思考能力的模型,在输出结果中包含了模型本身的推理、思考过程,这样的模型位推理模型。
阿里云百炼自己的推理模型:qwq-plus
注:
qwq-plus是阿里云百炼中QwQ系列的推理模型调用名;现在也可以使用qwen-plus并通过enable_thinking=true启深度思考模式。
2.1.模型相应格式
推理模型返回结果中包含两部分:
- 思考流程
- 响应结果 相应格式:
{
"choices": [
{
"message": {
"content": "9.9比9.11大。",
"reasoning_content": "\n嗯,用户问的是9.9和9.11谁大。这个问题看起来好像挺简单的,但可能有些小陷阱,特别是涉及到小数点的比较。首先,我需要确认用户是否在问数字的大小比较,还是有没有其他可能的含义,比如日期之类的。不过根据数字的写法,应该是数字比较。...\n",
"role": "assistant"
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"object": "chat.completion",
"usage": {
"prompt_tokens": 19,
"completion_tokens": 797,
"total_tokens": 816
},
"created": 1739069910,
"system_fingerprint": null,
"model": "deepseek-r1",
"id": "chatcmpl-e55cdb8a-9ce1-9662-b87c-cf3da706e4f3"
}注意,在message中包含两部分内容:
- content:响应结果
- reasoning_content:思考过程
2.2.自定义Advisor处理思考过程
推理模型的响应中,思考过程通常不在普通content字段里,而是在 reasoning_content字段中。
Spring AI/Spring AI Alibaba 会把reasoning_content映射到AssistantMessage的 metadata中,key通常是reasoningContent。
因此,如果我们直接使用chatClient.prompt().user(...).call().content(),默认只能拿到最终回答,看不到思考过程。
如果希望前端通过普通content一起接收到思考内容,就可以自定义Advisor,在响应返回后从metadata中取出reasoningContent,再拼接到普通content中返回。
两个版本的Advisor:
- 版本1:阿里巴巴官方文档提供,在stream模式下思维链会呈现片段化
$ curl http://localhost:10002/qwq/chat-client/stream/chat
<think>好的,用户让我</think>
<think>介绍自己,我之前</think>
<think>已经回答过一次了</think>
<think>,现在又问</think>
<think>同样的问题。用户</think>
<think>可能是想再确认一下</think>
<think>我的功能,或者需要</think>
<think>更详细的介绍?</think>
<think>也有可能他们想</think>
<think>测试我的一致性hink>
<think>又全面,同时保持简洁</think>
<think>。首先,回顾之前的回答</think>
<think>,已经涵盖了基本</think>
<think>功能、支持的语言、应用场景</think>
<think>。这次可能需要添加</think>
<think>一些信息,比如</think>
<think>最近的更新或者</think>
<think>更多例子,让用户</think>
<think>觉得有新内容</think>
<think>。不过根据指示</think>
<think>,不能编造新</think>
<think>功能,所以只能</think>
<think>在原有基础上调整</think>
<think>结构或补充细节。</think>
<think>用户可能希望了解我的应用场景</think>
<think>,或者想确认</think>
<think>我的能力是否符合他们的</think>
<think>需求。需要强调</think>
<think>我的多语能力和</think>
<think>具体应用实例,比如编程</think>
<think>、逻辑推理等。</think>
<think>另外,可以加入</think>
<think>一些鼓励用户提问的</think> >
<think>语句,促进进一步互动</think>
<think>。检查是否有需要</think>
<think>避免的内容,比如不</think>
<think>提及未实现的功能。</think>
<think>确保语气友好,使用</think>
<think>表情符号增加亲切</think>
<think>。最后,保持回答自然</think>
<think>流畅,避免重复之前的</think>
<think>结构,但信息</think>
<think>要准确一致。</think>
你好!我是是义千问(Qwen),阿里巴巴集团旗下的超大规模语言模型。我能够帮助你完成各种任务,比如:
- **回答问题**:无论是常识、专业知识,还是复杂问题,我都会尽力为你解答。
- **创作文字**:写故事、公文、邮件、剧本、诗歌等,我都可以尝试。
- **逻辑与编程**:解决数学问题、编写代码、进行逻辑推理。
- **多语言支持**:除了中文,我还支持英文、德语、法语、西班牙语等多种语言。
- **表达观点与互动**:聊日常话题、玩游戏,甚至讨论观点。
我的目标是成为一位全能的AI助手,无论你需要学习、工作还是娱乐上的帮助,我都会用友好且实用的方式回应你。有什么需要我帮忙的吗?😊- 版本2:自定义版本,在stream模式下,思维链还是一个整体:
$ curl http://localhost:10002/qwq/chat-client/stream/chat
<think>好的,用户让我介绍自己,我之前已经回答过一次了,现在又问同样的问题。用户可能是想再确认一下我的功能,或者需要更详细的介绍?也有可能他们想测试我的一致性。首先,回顾之前的回答,已经涵盖了基本功能、支持的语言、应用场景。这次可能需要添加一些信息,比如最近的更新或者更多例子,让用户觉得有新内容。不过根据指示,不能编造新功能,所以只能在原有基础上调整结构或补充细节。用户可能希望了解我的应用场景,或者想确认我的能力是否符合他们的需求。需要强调我的多语能力和具体应用实例,比如编程、逻辑推理等。另外,可以加入一些鼓励用户提问的语句,促进进一步互动。检查是否有需要避免的内容,比如不提及未实现的功能。确保语气友好,使用表情符号增加亲切。最后,保持回答自然流畅,避免重复之前的结构,但信息要准确一致。</think>
你好!我是是义千问(Qwen),阿里巴巴集团旗下的超大规模语言模型。我能够帮助你完成各种任务,比如:
- **回答问题**:无论是常识、专业知识,还是复杂问题,我都会尽力为你解答。
- **创作文字**:写故事、公文、邮件、剧本、诗歌等,我都可以尝试。
- **逻辑与编程**:解决数学问题、编写代码、进行逻辑推理。
- **多语言支持**:除了中文,我还支持英文、德语、法语、西班牙语等多种语言。
- **表达观点与互动**:聊日常话题、玩游戏,甚至讨论观点。
我的目标是成为一位全能的AI助手,无论你需要学习、工作还是娱乐上的帮助,我都会用友好且实用的方式回应你。有什么需要我帮忙的吗?😊2.2.1.官方版本
在advisor包下新建ReasoningContentAdvisor类:
package com.example.advisor;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.util.StringUtils;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
public class ReasoningContentAdvisor implements BaseAdvisor {
private final int order;
public ReasoningContentAdvisor(Integer order) {
this.order = order != null ? order : 0;
}
@NotNull
@Override public AdvisedRequest before(@NotNull AdvisedRequest request) {
return request;
}
@NotNull
@Override public AdvisedResponse after(AdvisedResponse advisedResponse) {
ChatResponse resp = advisedResponse.response();
if (Objects.isNull(resp)) {
return advisedResponse;
}
String reasoningContent = String.valueOf(resp.getResults().get(0).getOutput().getMetadata().get("reasoningContent"));
if (StringUtils.hasText(reasoningContent)) {
List<Generation> thinkGenerations = resp.getResults().stream()
.map(generation -> {
AssistantMessage output = generation.getOutput();
AssistantMessage thinkAssistantMessage = new AssistantMessage(
String.format("<think>%s</think>", reasoningContent) + output.getText(),
output.getMetadata(),
output.getToolCalls(),
output.getMedia()
);
return new Generation(thinkAssistantMessage, generation.getMetadata());
}).toList();
ChatResponse thinkChatResp = ChatResponse.builder().from(resp).generations(thinkGenerations).build();
return AdvisedResponse.from(advisedResponse).response(thinkChatResp).build();
}
return advisedResponse;
}
@Override
public int getOrder() {
return this.order;
}
}2.2.2.自定义版本
package com.example.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.util.StringUtils;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
@Slf4j
public class ReasoningContentAdvisor implements BaseAdvisor {
private final int order;
private final ThreadLocal<Boolean> isThinking = ThreadLocal.withInitial(() -> false);
public ReasoningContentAdvisor(Integer order) {
this.order = order != null ? order : 0;
}
@NotNull
@Override public AdvisedRequest before(@NotNull AdvisedRequest request) {
return request;
}
@NotNull
@Override public AdvisedResponse after(AdvisedResponse advisedResponse) {
ChatResponse resp = advisedResponse.response();
if (Objects.isNull(resp)) {
return advisedResponse;
}
String reasoningContent = String.valueOf(resp.getResults().get(0).getOutput().getMetadata().get("reasoningContent"));
String textContent = resp.getResults().get(0).getOutput().getText();
if (StringUtils.hasText(reasoningContent)) {
if (!isThinking.get()) {
reasoningContent = "<think>" + reasoningContent;
isThinking.set(true);
}
return AdvisedResponse.from(advisedResponse)
.response(
ChatResponse.builder()
.from(resp)
.generations(rebuildGeneration(resp, reasoningContent + textContent))
.build())
.build();
}
if (isThinking.get()) {
isThinking.set(false);
return AdvisedResponse.from(advisedResponse)
.response(
ChatResponse.builder()
.from(resp)
.generations(rebuildGeneration(resp, reasoningContent + "</think>" + textContent))
.build())
.build();
}
return advisedResponse;
}
private static List<Generation> rebuildGeneration(ChatResponse resp, String message) {
return resp.getResults().stream()
.map(generation -> {
AssistantMessage output = generation.getOutput();
log.debug("output: {}", output);
AssistantMessage thinkAssistantMessage = new AssistantMessage(message,
output.getMetadata(),
output.getToolCalls(),
output.getMedia()
);
return new Generation(thinkAssistantMessage, generation.getMetadata());
}).toList();
}
@Override
public int getOrder() {
return this.order;
}
}注意:当前版本是基于ThreadLocal存储思考标记,可能存在内存占用或线程安全风险,谨慎使用。
2.3.配置Advisor
修改CommonConfiguration中的ChatClient配置:
@Bean
public ChatClient chatClient(DashScopeChatModel chatModel,ChatMemory chatMemory){
return ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor(),
new MessageChatMemoryAdvisor(chatMemory),
new ReasoningContentAdvisor(0)
).build();
}2.4.测试:
官方Advisor效果: 
自定义Advisor效果: 
参考文档:SpringAI+DeepSeek