Skip to content
DAILY QUOTE

“ ”

之前对接阿里云百炼平台一直都是用SpringAI的OpenAI模块,不过由于阿里云百炼与OpenAI之间不是完全兼容,所以还存在许多问题。

为了更好的与阿里云百炼平台对接,同时又能兼容SpringAI,阿里巴巴就在SpringAI的基础上推出了自己的集成API,Spring AI Alibaba。

1.快速入门

1.1.创建工程

首先创建一个SpringBoot工程: 选择Spring Web依赖,另一个是AI依赖。由于SpringAI默认不支持alibaba的百炼,所以我们先勾选OpenAI依赖,等会再修改。

1.2.引入依赖

接下里用alibaba的依赖取代OpenAI:

XML
<!--注释或删除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配置,内容如下:

yaml
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: debug

1.4.配置ChatClient

在config包下新建一个CommonConfiguration类:

java
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类:

java
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.模型相应格式

推理模型返回结果中包含两部分:

  • 思考流程
  • 响应结果 相应格式:
JSON
{
  "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模式下思维链会呈现片段化
Java
$ 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模式下,思维链还是一个整体:
Java
$ curl http://localhost:10002/qwq/chat-client/stream/chat

<think>好的,用户让我介绍自己,我之前已经回答过一次了,现在又问同样的问题。用户可能是想再确认一下我的功能,或者需要更详细的介绍?也有可能他们想测试我的一致性。首先,回顾之前的回答,已经涵盖了基本功能、支持的语言、应用场景。这次可能需要添加一些信息,比如最近的更新或者更多例子,让用户觉得有新内容。不过根据指示,不能编造新功能,所以只能在原有基础上调整结构或补充细节。用户可能希望了解我的应用场景,或者想确认我的能力是否符合他们的需求。需要强调我的多语能力和具体应用实例,比如编程、逻辑推理等。另外,可以加入一些鼓励用户提问的语句,促进进一步互动。检查是否有需要避免的内容,比如不提及未实现的功能。确保语气友好,使用表情符号增加亲切。最后,保持回答自然流畅,避免重复之前的结构,但信息要准确一致。</think>

你好!我是是义千问(Qwen),阿里巴巴集团旗下的超大规模语言模型。我能够帮助你完成各种任务,比如:

- **回答问题**:无论是常识、专业知识,还是复杂问题,我都会尽力为你解答。
- **创作文字**:写故事、公文、邮件、剧本、诗歌等,我都可以尝试。
- **逻辑与编程**:解决数学问题、编写代码、进行逻辑推理。
- **多语言支持**:除了中文,我还支持英文、德语、法语、西班牙语等多种语言。
- **表达观点与互动**:聊日常话题、玩游戏,甚至讨论观点。

我的目标是成为一位全能的AI助手,无论你需要学习、工作还是娱乐上的帮助,我都会用友好且实用的方式回应你。有什么需要我帮忙的吗?😊

2.2.1.官方版本

在advisor包下新建ReasoningContentAdvisor类:

Java
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.自定义版本

java
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配置:

java
@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