学习链接
阿里AI-Spring Cloud Alibaba AI:快速搭建自己的通义千问
SpringCloud + Spring AI Alibaba 整合阿里云百炼大模型
Spring AI Alibaba官方文档
spring-ai-alibaba 官方github
spring-ai-alibaba-examples - spring ai alibaba示例代码
spring-cloud-alibaba-examples/spring-cloud-ai-example(忽略这个)
Spring Cloud Alibaba官网
Spring Boot 单体应用升级 Spring Cloud 微服务最佳实践
阿里百炼
创建api-key
查看api调用示例
jdk版本使用17(如果使用jdk8,okhttp版本和gson版本不兼容,要么这个类找不到,要么那个方法没有。更换了版本也总会有其它地方的问题。)
示例
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 用java8 总是 okhttp方法找不到,类找不到,有时这个版本能用,但另外的问题还存在,换了版本,方法又不存在。 -->
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.4.2</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo-alibailian</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
<exclusions>
<!-- 排除slf4j-simple依赖 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
AQuickStart
package com.zzhua;// dashscope SDK的版本 >= 2.18.2
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.zzhua.constant.Constants;
import java.util.Arrays;
/*
测试:
curl -X POST "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" \
-H "Authorization: Bearer sk-~~~" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1",
"input":{
"messages":[
{
"role": "user",
"content": "你是谁?"
}
]
},
"parameters": {
"result_format": "message"
}
}'
*/
public class AQuickStart {
public static GenerationResult callWithMessage() throws Exception {
Generation gen = new Generation();
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content("你是谁?")
.build();
GenerationParam param = GenerationParam.builder()
// 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
.apiKey(Constants.API_KEY)
.model("deepseek-r1")
.prompt("你是谁?")
.messages(Arrays.asList(userMsg))
// 不可以设置为"text"
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
return gen.call(param);
}
public static void main(String[] args) {
try {
GenerationResult result = callWithMessage();
System.out.println("思考过程:");
System.out.println(result.getOutput().getChoices().get(0).getMessage().getReasoningContent());
System.out.println("回复内容:");
System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent());
} catch (Exception e) {
// 使用日志框架记录异常信息
System.err.println("An error occurred while calling the generation service: " + e.getMessage());
}
System.exit(0);
}
}
MultiChat
package com.zzhua;// dashscope SDK的版本 >= 2.18.2
import java.util.Arrays;
import java.lang.System;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.JsonUtils;
import com.zzhua.constant.Constants;
public class MultiChat {
public static GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException {
Generation gen = new Generation();
Message userMsg1 = Message.builder()
.role(Role.USER.getValue())
.content("你好")
.build();
Message AssistantMsg = Message.builder()
.role(Role.ASSISTANT.getValue())
.content("你好!很高兴见到你,有什么我可以帮忙的吗?")
.build();
Message UserMsg2 = Message.builder()
.role(Role.USER.getValue())
.content("你是谁")
.build();
GenerationParam param = GenerationParam.builder()
// 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
.apiKey(Constants.API_KEY)
.model("deepseek-r1")
.messages(Arrays.asList(userMsg1, AssistantMsg, UserMsg2))
// 不可以设置为"text"
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
return gen.call(param);
}
public static void main(String[] args) {
try {
GenerationResult result = callWithMessage();
System.out.println("思考过程:");
System.out.println(result.getOutput().getChoices().get(0).getMessage().getReasoningContent());
System.out.println("回复内容:");
System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent());
} catch (ApiException | NoApiKeyException | InputRequiredException e) {
// 使用日志框架记录异常信息
System.err.println("An error occurred while calling the generation service: " + e.getMessage());
}
System.exit(0);
}
}
StreamChat
package com.zzhua;// dashscope SDK的版本 >= 2.18.2
import java.util.Arrays;
import com.zzhua.constant.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import io.reactivex.Flowable;
import java.lang.System;
public class StreamChat {
private static final Logger logger = LoggerFactory.getLogger(StreamChat.class);
private static StringBuilder reasoningContent = new StringBuilder();
private static StringBuilder finalContent = new StringBuilder();
private static boolean isFirstPrint = true;
private static void handleGenerationResult(GenerationResult message) {
String reasoning = message.getOutput().getChoices().get(0).getMessage().getReasoningContent();
String content = message.getOutput().getChoices().get(0).getMessage().getContent();
if (!reasoning.isEmpty()) {
reasoningContent.append(reasoning);
if (isFirstPrint) {
System.out.println("====================思考过程====================");
isFirstPrint = false;
}
System.out.print(reasoning);
}
if (!content.isEmpty()) {
finalContent.append(content);
if (!isFirstPrint) {
System.out.println("\n====================完整回复====================");
isFirstPrint = true;
}
System.out.print(content);
}
}
private static GenerationParam buildGenerationParam(Message userMsg) {
return GenerationParam.builder()
// 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
.apiKey(Constants.API_KEY)
.model("deepseek-r1")
.messages(Arrays.asList(userMsg))
// 不可以设置为"text"
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.incrementalOutput(true)
.build();
}
public static void streamCallWithMessage(Generation gen, Message userMsg)
throws NoApiKeyException, ApiException, InputRequiredException {
GenerationParam param = buildGenerationParam(userMsg);
Flowable<GenerationResult> result = gen.streamCall(param);
result.blockingForEach(message -> handleGenerationResult(message));
}
public static void main(String[] args) {
try {
Generation gen = new Generation();
Message userMsg = Message.builder().role(Role.USER.getValue()).content("你是谁?").build();
streamCallWithMessage(gen, userMsg);
// 打印最终结果
// if (reasoningContent.length() > 0) {
// System.out.println("\n====================完整回复====================");
// System.out.println(finalContent.toString());
// }
} catch (ApiException | NoApiKeyException | InputRequiredException e) {
logger.error("An exception occurred: {}", e.getMessage());
}
System.exit(0);
}
}
Spring AI Alibaba
1、注意:因为 Spring AI Alibaba 基于 Spring Boot 3.x 开发,因此本地 JDK 版本要求为 17 及以上。
2、spring-ai-alibaba-examples:此仓库中包含许多 Example 来介绍 Spring AI Alibaba 从基础到高级的各种用法和 AI 项目的最佳实践。 更详细的介绍介绍请参阅每个子项目中的 README.md 和 Spring AI Alibaba 官网。
3、 阿里云百炼平台 获取 API-KEY
4、由于 spring-ai 相关依赖包还没有发布到中央仓库,如出现 spring-ai-core 等相关依赖解析问题,请在您项目的 pom.xml 依赖中加入指定的仓库配置。
简单示例
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.4.2</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo-alibailian</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-ai-alibaba-starter 传递依赖了dashscope-sdk-java, 但是可能不是需要的版本(不是最新的版本) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
<exclusions>
<!-- 排除slf4j-simple依赖 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M5.1</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml
spring:
ai:
dashscope:
api-key: sk-~~~ # 阿里百炼的key
HelloworldController
@RestController
@RequestMapping("/helloworld")
public class HelloworldController {
private static final String DEFAULT_PROMPT = "你是一个博学的智能聊天助手,请根据用户提问回答!";
private final ChatClient dashScopeChatClient;
// ChatClientAutoConfiguration中注册了scope为prototype的ChatClient.Builder,
// 同时该ChatClient.Builder会依赖1个 ChatModel, 此ChatModel在DashScopeAutoConfiguration中定义了DashScopeChatModel这个bean,
// 所以,当需要ChatClient.Builder时,就会触发多例bean的获取,从而会寻找ChatModel的实现bean,如果有不止1个ChatModel的bean定义了,那么就会报错
public HelloworldController(ChatClient.Builder chatClientBuilder) {
this.dashScopeChatClient = chatClientBuilder
.defaultSystem(DEFAULT_PROMPT)
// 实现 Chat Memory 的 Advisor
// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
// 实现 Logger 的 Advisor
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
DashScopeChatOptions.builder()
.withTopP(0.7)
.build()
)
.build();
}
// 也可以如下这样创建,与上面是1个意思
/*public HelloworldController(ChatModel chatModel) {
// 构造时,可以设置 ChatClient 的参数
// {@link org.springframework.ai.chat.client.ChatClient};
this.dashScopeChatClient = ChatClient.builder(chatModel)
// 实现 Chat Memory 的 Advisor
// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
// 实现 Logger 的 Advisor
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
DashScopeChatOptions.builder()
.withTopP(0.7)
.build()
)
.build();
}*/
// http://localhost:8080/helloworld/simple/chat?query=你是谁
/**
* ChatClient 简单调用
*/
@GetMapping("/simple/chat")
public String simpleChat(@RequestParam("query") String query) {
// 1、DefaultChatClientRequestSpec 构造方法中添加的 CallAroundAdvisor 触发了请求的执行
// 调用 chatModel.call(Prompt),即 DashScopeChatModel#call(Prompt)
// 2、然后调用 DashScopeApi#chatCompletionEntity(ChatCompletionRequest)
// 可以看到 最终默认调用的模型是 qwen-plus
// (是因为 DashScopeAutoConfiguration 定义了 DashScopeChatModel 这个bean,
// 而 DashScopeChatModel 这个bean又引用了 DashScopeChatProperties 这个bean,
// 而 DashScopeChatProperties 中直接初始化的 DashScopeChatOptions 使用了 qwen-plus。
// 所以,如果需要改掉模型,方法就是 1. 通过spring.ai.datascope.chat.options.model设置 来改掉默认的模型
// 2. 或者这样改 dashScopeChatClient.prompt(new Prompt(query, ChatOptions.builder().model("deepseek-r1").build())).call().content()
// 3、注意几个类: DashScopeAutoConfiguration、
// DashScopeConnectionProperties(配置前缀是 spring.ai.datascope)、
// DashScopeChatProperties(配置前缀是 spring.ai.datascope.chat)
// return dashScopeChatClient.prompt(query).call().content();
return dashScopeChatClient.prompt(new Prompt(query, ChatOptions.builder().model("deepseek-r1").build())).call().content();
}
// http://localhost:8080/helloworld/stream/chat?query=你是谁
/**
* ChatClient 流式调用
*/
@GetMapping("/stream/chat")
public Flux<String> streamChat(@RequestParam(value = "query", defaultValue = "你好,很高兴认识你,能简单介绍一下自己吗?") String query, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
return dashScopeChatClient.prompt(query).stream().content();
}
// http://127.0.0.1:8080/helloworld/advisor/chat/123?query=你好,我叫牧生,之后的会话中都带上我的名字
// http://127.0.0.1:8080/helloworld/advisor/chat/123?query=我叫什么名字?
/**
* ChatClient 使用自定义的 Advisor 实现功能增强.
* eg:
* http://127.0.0.1:8080/helloworld/advisor/chat/123?query=你好,我叫牧生,之后的会话中都带上我的名字
* 你好,牧生!很高兴认识你。在接下来的对话中,我会记得带上你的名字。有什么想聊的吗?
* http://127.0.0.1:8080/helloworld/advisor/chat/123?query=我叫什么名字?
* 你叫牧生呀。有什么事情想要分享或者讨论吗,牧生?
*/
@GetMapping("/advisor/chat/{id}")
public Flux<String> advisorChat(
HttpServletResponse response,
@PathVariable(value = "id") String id,
@RequestParam(value = "query") String query) {
response.setCharacterEncoding("UTF-8");
return this.dashScopeChatClient.prompt(query)
// 因为前面使用Client.Builder构建Client时, 添加了MessageChatMemoryAdvisor切面
// 所以, 这里可以添加切面参数
.advisors(
a -> a
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, id)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
).stream().content();
}
}
DashScopeChatModelController
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import jakarta.servlet.http.HttpServletResponse;
import reactor.core.publisher.Flux;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/dashscope/chat-model")
public class DashScopeChatModelController {
private static final String DEFAULT_PROMPT = "你好,介绍下你自己吧。";
private final ChatModel dashScopeChatModel;
public DashScopeChatModelController(ChatModel chatModel) {
this.dashScopeChatModel = chatModel;
}
// http://localhost:8080/dashscope/chat-model/simple/chat
/**
* 最简单的使用方式,没有任何 LLMs 参数注入。
* @return String types.
*/
@GetMapping("/simple/chat")
public String simpleChat() {
return dashScopeChatModel.call(new Prompt(DEFAULT_PROMPT, DashScopeChatOptions
.builder()
// .withModel("qwen2.5-vl-72b-instruct") // 这个会报错, 这应该是模型的问题
.withModel("qwen-plus") // 这个正常回答: 你好!我是通义千问,阿里巴巴集团旗下...
// .withModel("deepseek-r1") // 这个正常回答: 您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任...
// .withModel("deepseek-r1-distill-qwen-1.5b") // 这个正常回答: 我是DeepSeek-R1,一个由深度求索公司开发的智能助手,我...
.build())).getResult().getOutput().getContent();
}
// http://localhost:8080/dashscope/chat-model/stream/chat
/**
* Stream 流式调用。可以使大模型的输出信息实现打字机效果。
* @return Flux<String> types.
*/
@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {
// 避免返回乱码
response.setCharacterEncoding("UTF-8");
Flux<ChatResponse> stream = dashScopeChatModel.stream(new Prompt(DEFAULT_PROMPT, DashScopeChatOptions
.builder()
// .withModel("qwen-plus") // 这个正常回答: 你好!我是通义千问,阿里巴巴集团旗下...
// .withModel("deepseek-r1") // 这个正常回答: 您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任...
.withModel("deepseek-r1-distill-qwen-1.5b") // /这个正常回答: 我是DeepSeek-R1,...
.build()));
return stream.map(resp -> resp.getResult().getOutput().getContent());
}
// http://localhost:8080/dashscope/chat-model/custom/chat
/**
* 使用编程方式自定义 LLMs ChatOptions 参数, {@link com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions}
* 优先级高于在 application.yml 中配置的 LLMs 参数!
*/
@GetMapping("/custom/chat")
public String customChat() {
// 不指定模型, 将会使用默认的 spring.ai.dashscope.chat.options.model 配置的模型(默认是qwen-plus)
DashScopeChatOptions customOptions = DashScopeChatOptions.builder()
.withTopP(0.7)
.withTopK(50)
.withTemperature(0.8)
.build();
return dashScopeChatModel.call(new Prompt(DEFAULT_PROMPT, customOptions)).getResult().getOutput().getContent();
}
}
图解spring AI的结构
deepseek
这节示例是使用OpenAi对接deepseek,跟dashscope没关系(所以这个依赖可以不用引入),需要引入spring-ai-openai-spring-boot-starter
。如果同时引入spring-ai-alibaba-starter
就需要注意 注入的ChatModel的具体实现。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.4.2</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo-alibailian</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
<exclusions>
<!– 排除slf4j-simple依赖 –>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M5.1</version>
</dependency>
<!-- 引入spring-ai openai的启动依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M5</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml
spring:
ai:
# 百炼的配置
dashscope:
api-key: sk-~~~# 百炼的ApiKey
chat:
options:
model: qwen-plus
# 可兼容openai的配置
openai:
api-key: sk-~~~# deepseek的ApiKey
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
embedding:
enabled: false
DeepSeekChatClientController
@RestController
public class DeepSeekChatClientController {
private static final String DEFAULT_PROMPT = "你好,介绍下你自己!";
private final ChatModel chatModel;
private final ChatClient deepSeekChatClient;
// 【注入的是 OpenAiChatModel, 而这里配置的是与openai兼容的deepseek。
// OpenAiAutoConfiguration 中定义了 OpenAiChatModel。
// ChatModel(这个由各个spring-ai模块包自己实现,并通过自动配置类提供出来)
// -> ChatClient.Builder(这个在 ClientAutoConfiguration 中定义为多例bean,它需要1个 ChatModel)
// -> ChatClient(使用 ChatClient.Builder 构建,其实现为 DefaultChatClient)】
public DeepSeekChatClientController (OpenAiChatModel chatModel) {
this.chatModel = chatModel;
// 这里实际上就是在模拟 ClientAutoConfiguration 中定义 ChatClient.Builder多例bean 类似
this.deepSeekChatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
// 实现 Logger 的 Advisor
.defaultAdvisors(new SimpleLoggerAdvisor())
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(OpenAiChatOptions.builder().temperature(0.7d).build()).build();
}
// http://localhost:8080/ai/customOptions
/**
* 使用自定义参数调用DeepSeek模型
*
* @return ChatResponse 包含模型响应结果的封装对象
* @apiNote 当前硬编码指定模型为deepseek-chat,温度参数0.7以平衡生成结果的创造性和稳定性
*/
@GetMapping(value = "/ai/customOptions")
public ChatResponse testDeepSeekCustomOptions () {
return this.deepSeekChatClient.prompt(DEFAULT_PROMPT).call().chatResponse();
}
// http://localhost:8080/ai/generate
/**
* 执行默认提示语的AI生成请求
*
* @return Map 包含生成结果的键值对,格式为{ "generation": 响应内容 }
*/
@GetMapping("/ai/generate")
public Map<String, Object> testEasyChat () {
return Map.of("generation", this.deepSeekChatClient.prompt(DEFAULT_PROMPT).call().content());
}
// http://localhost:8080/ai/stream
/**
* 流式生成接口 - 支持实时获取生成过程的分块响应
*
* @return Flux<ChatResponse> 响应式流对象,包含分块的模型响应数据
* @see Flux 基于Project Reactor的响应式流对象
*/
@GetMapping("/ai/stream")
public Flux<String> testDeepSeekGenerateWithStream (HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
// 1、DefaultChatClientRequestSpec 构造方法中添加的 StreamAroundAdvisor 触发了请求的执行
// 调用 chatModel.stream(Prompt),即 chatModel#stream(Prompt),这里的chatModel就是OpenAiChatModel的实现
// 2、所以具体怎么解析响应的 需要到 OpenAiChatModel#stream(Prompt) 方法中查看
// 3、所以,其实就发现 chatClient还绕了一圈,然后再去调用chatModel,绕一圈的目的是为了增强功能
return deepSeekChatClient.prompt(DEFAULT_PROMPT).stream().chatResponse()
.map(chatResponse -> chatResponse.getResult().getOutput().getContent());
}
}
DeepSeekChatModelController
@RestController
public class DeepSeekChatModelController {
private static final String DEFAULT_PROMPT = "你好,介绍下你自己吧。";
private final ChatModel deepSeekChatModel;
// 注入的是 OpenAiChatModel
public DeepSeekChatModelController (OpenAiChatModel chatModel) {
this.deepSeekChatModel = chatModel;
}
// http://localhost:8080/simple/chat
/**
* 最简单的使用方式,没有任何 LLMs 参数注入。
*
* @return String types.
*/
@GetMapping("/simple/chat")
public String simpleChat () {
return deepSeekChatModel.call(new Prompt(DEFAULT_PROMPT)).getResult().getOutput().getContent();
}
// http://localhost:8080/stream/chat
/**
* Stream 流式调用。可以使大模型的输出信息实现打字机效果。
*
* @return Flux<String> types.
*/
@GetMapping("/stream/chat")
public Flux<String> streamChat (HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
Flux<ChatResponse> stream = deepSeekChatModel.stream(new Prompt(DEFAULT_PROMPT));
return stream.map(resp -> resp.getResult().getOutput().getContent());
}
// http://localhost:8080/custom/chat
/**
* 使用编程方式自定义 LLMs ChatOptions 参数, {OpenAIChatOption}
* 优先级高于在 application.yml 中配置的 LLMs 参数!
*/
@GetMapping("/custom/chat")
public String customChat () {
// 指定的模型 将覆盖 chatModel 中配置的模型
// 模型名优先级顺序 Prompt中的options > client配置的options > chatModel中的options(通过配置文件配置的)
OpenAiChatOptions customOptions = OpenAiChatOptions.builder().model("deepseek-reasoner").temperature(0.8d).build();
return deepSeekChatModel.call(new Prompt(DEFAULT_PROMPT, customOptions)).getResult().getOutput().getContent();
}
}
dashscope文生图
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.4.2</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo-alibailian</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
<exclusions>
<!-- 排除slf4j-simple依赖 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M5.1</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml
spring:
ai:
# 百炼的配置
dashscope:
api-key: sk-~~~# 百炼的ApiKey
DashScopeImageController
可以看出来,spring ai alibaba其实就是实现了spring ai core中的接口
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/example")
public class DashScopeImageController {
private final ImageModel imageModel;
private static final String DEFAULT_PROMPT = "为人工智能生成一张富有科技感的图片!";
public DashScopeImageController(ImageModel imageModel) {
this.imageModel = imageModel;
}
// http://localhost:8080/example/image?text=生成1张小怪兽打奥特曼的图片
@GetMapping("/image")
public void image(@RequestParam(value = "text", defaultValue = DEFAULT_PROMPT) String text, HttpServletResponse response) {
ImageResponse imageResponse = imageModel.call(new ImagePrompt(text));
String imageUrl = imageResponse.getResult().getOutput().getUrl();
System.out.println(imageUrl);
try {
URL url = URI.create(imageUrl).toURL();
InputStream in = url.openStream();
response.setHeader("Content-Type", MediaType.IMAGE_PNG_VALUE);
response.getOutputStream().write(in.readAllBytes());
response.getOutputStream().flush();
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
dashscope语音
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.4.2</version>
</parent>
<groupId>org.example</groupId>
<artifactId>demo-alibailian</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
<exclusions>
<!-- 排除slf4j-simple依赖 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M5.1</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml
spring:
ai:
# 百炼的配置
dashscope:
api-key: sk-~~~# 百炼的ApiKey
TTSController
@RestController
@RequestMapping("/ai/tts")
public class TTSController /*implements ApplicationRunner*/ {
private final SpeechSynthesisModel speechSynthesisModel;
private static final String TEXT = "白日依山尽,黄河入海流。";
private static final String FILE_PATH = System.getProperty("user.dir") + "/file/";
public TTSController(SpeechSynthesisModel speechSynthesisModel) {
this.speechSynthesisModel = speechSynthesisModel;
}
// http://localhost:8080/ai/tts?text=白日依山尽,黄河入海流。
@GetMapping
public void tts(@RequestParam(value = "text", defaultValue = TEXT) String text) throws IOException {
SpeechSynthesisResponse response = speechSynthesisModel.call(
new SpeechSynthesisPrompt(text)
);
File file = new File(FILE_PATH + "/output.mp3");
try (FileOutputStream fos = new FileOutputStream(file)) {
ByteBuffer byteBuffer = response.getResult().getOutput().getAudio();
fos.write(byteBuffer.array());
}
catch (IOException e) {
throw new IOException(e.getMessage());
}
}
@GetMapping("/stream")
public void streamTTS(@RequestParam(value = "text", defaultValue = TEXT) String text) {
Flux<SpeechSynthesisResponse> response = speechSynthesisModel.stream(
new SpeechSynthesisPrompt(text)
);
CountDownLatch latch = new CountDownLatch(1);
File file = new File(FILE_PATH + "/output-stream.mp3");
try (FileOutputStream fos = new FileOutputStream(file)) {
response.doFinally(
signal -> latch.countDown()
).subscribe(synthesisResponse -> {
ByteBuffer byteBuffer = synthesisResponse.getResult().getOutput().getAudio();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
fos.write(bytes);
}
catch (IOException e) {
throw new RuntimeException(e);
}
});
latch.await();
}
catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
/*@Override
public void run(ApplicationArguments args) {
File file = new File(FILE_PATH);
if (!file.exists()) {
file.mkdirs();
}
}
@PreDestroy
public void destroy() throws IOException {
FileUtils.deleteDirectory(new File(FILE_PATH));
}
*/
}
STTController
@RestController
@RequestMapping("/ai/stt")
public class STTController {
private final AudioTranscriptionModel transcriptionModel;
private static final Logger log = LoggerFactory.getLogger(STTController.class);
private static final String DEFAULT_MODEL_1 = "sensevoice-v1";
private static final String DEFAULT_MODEL_2 = "paraformer-realtime-v2";
private static final String DEFAULT_MODEL_3 = "paraformer-v2";
private static final String AUDIO_RESOURCES_URL = "https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/hello_world_female2.wav";
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public STTController(AudioTranscriptionModel transcriptionModel) {
this.transcriptionModel = transcriptionModel;
}
@GetMapping
public String stt() throws MalformedURLException {
AudioTranscriptionResponse response = transcriptionModel.call(
new AudioTranscriptionPrompt(
new UrlResource(AUDIO_RESOURCES_URL),
DashScopeAudioTranscriptionOptions.builder()
.withModel(DEFAULT_MODEL_1)
.build()
)
);
return response.getResult().getOutput();
}
@GetMapping("/stream")
public String streamSTT() {
CountDownLatch latch = new CountDownLatch(1);
StringBuilder stringBuilder = new StringBuilder();
Flux<AudioTranscriptionResponse> response = transcriptionModel
.stream(
new AudioTranscriptionPrompt(
new FileSystemResource("spring-ai-alibaba-audio-example/dashscope-audio/src/main/resources/stt/count.pcm"),
DashScopeAudioTranscriptionOptions.builder()
.withModel(DEFAULT_MODEL_2)
.withSampleRate(16000)
.withFormat(DashScopeAudioTranscriptionOptions.AudioFormat.PCM)
.withDisfluencyRemovalEnabled(false)
.build()
)
);
response.doFinally(
signal -> latch.countDown()
).subscribe(
resp -> stringBuilder.append(resp.getResult().getOutput())
);
try {
latch.await();
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
return stringBuilder.toString();
}
@GetMapping("/async")
public String asyncSTT() {
StringBuilder stringBuilder = new StringBuilder();
CountDownLatch latch = new CountDownLatch(1);
try {
AudioTranscriptionResponse submitResponse = transcriptionModel.asyncCall(
new AudioTranscriptionPrompt(
new UrlResource(AUDIO_RESOURCES_URL),
DashScopeAudioTranscriptionOptions.builder()
.withModel(DEFAULT_MODEL_3)
.build()
)
);
DashScopeAudioTranscriptionApi.Response.Output submitOutput = Objects.requireNonNull(submitResponse.getMetadata()
.get("output"));
String taskId = submitOutput.taskId();
scheduler.scheduleAtFixedRate(
() -> checkTaskStatus(taskId, stringBuilder, latch), 0, 1, TimeUnit.SECONDS);
latch.await();
}
catch (MalformedURLException e) {
throw new DashScopeException("Error in URL format: " + e.getMessage());
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new DashScopeException("Thread was interrupted: " + e.getMessage());
}
finally {
scheduler.shutdown();
}
return stringBuilder.toString();
}
private void checkTaskStatus(String taskId, StringBuilder stringBuilder, CountDownLatch latch) {
try {
AudioTranscriptionResponse fetchResponse = transcriptionModel.fetch(taskId);
DashScopeAudioTranscriptionApi.Response.Output fetchOutput =
Objects.requireNonNull(fetchResponse.getMetadata().get("output"));
DashScopeAudioTranscriptionApi.TaskStatus taskStatus = fetchOutput.taskStatus();
if (taskStatus.equals(DashScopeAudioTranscriptionApi.TaskStatus.SUCCEEDED)) {
stringBuilder.append(fetchResponse.getResult().getOutput());
latch.countDown();
}
else if (taskStatus.equals(DashScopeAudioTranscriptionApi.TaskStatus.FAILED)) {
log.warn("Transcription failed.");
latch.countDown();
}
}
catch (Exception e) {
latch.countDown();
throw new RuntimeException("Error occurred while checking task status: " + e.getMessage());
}
}
}