Spring AI Alibaba框架整合百炼大模型平台
文章目录
-
- [Spring AI Alibaba框架整合百炼大模型平台](#Spring AI Alibaba框架整合百炼大模型平台)
版本springboot 3.5.x, jdk17, springaiAlibaba1.1.2.2
1.Chat聊天模型
yml配置
yml
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# 聊天模型
chat:
enabled: true
options:
# 指定使用的模型型号(如 qwen-turbo、qwen-plus、qwen-max 等)
model: qwen-plus-2025-07-28
temperature: 0.7 #温度参数(0~2),控制生成文本的随机性 越低输出越确定,越高越随机(默认 0.85)
top-p: 0.9 # 核采样(0~1),控制生成多样性(默认 0.8)
max-tokens: 2000 # 最大生成 Token 数
enable-search: false # 是否开启联网搜索(仅部分模型支持)
# 停止词,生成到这些词时提前终止
stop: []
stream: true #是否流式返回
# --- 高级参数 ---
repetition-penalty: 1.1 # 重复惩罚
seed: 12345 # 随机种子 (保证结果一致)
tools: [] # 工具调用配置 (Function Calling)
配置类
java
/**
* ChatClient 配置类
*
* ChatClient 是 Spring AI 推荐的现代调用方式,支持链式编程和插件机制。
* 相比直接使用 ChatModel,ChatClient 提供了更丰富的功能:
* - 链式调用 API
* - 默认系统提示词
* - Advisor 插件机制(日志、记忆、RAG 等)
*/
@Configuration
public class ChatClientConfig {
/**
* 创建 ChatClient Bean
*
* @param chatModel 自动注入的 ChatModel(由 DashScope Starter 提供)
* @return 配置好的 ChatClient 实例
*/
@Bean
public ChatClient chatClient(DashScopeChatModel chatModel) {
return ChatClient.builder(chatModel)
// 设置默认系统提示词,定义了 AI 的基础人设和行为规范
.defaultSystem("你是一个专业、友好的AI助手,请用中文回答问题。")
// 添加日志插件,自动打印每次请求和响应的详细信息,方便调试
// SimpleLoggerAdvisor 是 Spring AI 内置的日志 Advisor
.defaultAdvisors(
new SimpleLoggerAdvisor()
//自定义插件
//,new MyLoggerAdvisor()
)
// 设置默认选项(可选)
// 【设置默认参数】
.defaultOptions(ChatOptions.builder()
.temperature(0.7) // 温度:控制回答的随机性。0=确定,1=创造性高
.topP(0.9) // 核采样:控制词汇选择的多样性
.build())
.build();
}
/**
* 创建 ChatClient.Builder Bean,供需要动态构建 ChatClient 的场景使用
* (例如多轮对话中需要为每个会话创建独立的 ChatClient)
*/
@Bean
public ChatClient.Builder chatClientBuilder(DashScopeChatModel chatModel) {
return ChatClient.builder(chatModel);
}
}
1.获取回答
java
@Test
public void chatTest() {
String content = "讲一个笑话";
//String response2 = chatClient.prompt(content).call().content();
String response = chatClient
.prompt() // 1. 开始构建提示词请求
.user(content) // 2. 设置用户输入的问题
.call() // 3. 同步调用 AI 接口
.content(); // 4. 获取 AI 返回的文本内容
System.out.println("AI助手:" + response);
}
2.获取完整响应信息(含token用量)
java
/**
* 2. 获取完整响应信息(包含 Token 用量等元数据)
* 有时候你需要知道这次请求花了多少 Token,这时候可以获取完整的 ChatResponse 对象。
*/
@PostMapping("/detail")
public Map<String, Object> detailChat(@RequestBody ChatRequest request) {
// chatResponse() 返回的是 ChatResponse 对象,包含 Token 用量、模型名称等元数据
ChatResponse response = chatClient.prompt()
.user(request.getQuestion())
.call()
.chatResponse();
// 从 ChatResponse 中提取文本内容
String content = response.getResult().getOutput().getText();
return Map.of(
"question", request.getQuestion(),
"answer", content,
// metadata 中包含 token 用量等信息
"metadata", response.getMetadata()
);
}
测试结果
json
{
"question": "讲一个笑话",
"answer": "当然可以!来一个轻松又带点小哲理的冷笑话 😄:\n\n> 为什么数学老师离婚了? \n> 因为他发现------ \n> **"人生不是非黑即白,但他的世界里,只有0和1。"** \n> (前妻说:"你连'我生气了'都要我写成布尔表达式?!")\n\n😂 \n(附赠解释:0 和 1 是二进制基础,也常被程序员/理工人"过度理性化"地套用到生活里......结果------bug 出现在感情模块,且无法 debug 🐞)\n\n想听更多类型(谐音梗、动物版、程序员专属、或者适合讲给孩子听的)?我随时待命~",
"metadata": {
"id": "ba246ae9-b5fa-9ac3-8de7-f93faf1ec7f1",
"model": "",
"rateLimit": {
"tokensRemaining": 0,
"requestsLimit": 0,
"requestsReset": "PT0S",
"tokensReset": "PT0S",
"tokensLimit": 0,
"requestsRemaining": 0
},
"usage": {
"promptTokens": 30,
"completionTokens": 160,
"totalTokens": 190
},
"promptMetadata": [
],
"empty": true
}
}
3.流式响应
java
/**
* 3. 流式输出(SSE)
* 像 ChatGPT 网页版那样,一个字一个字地显示回答,体验更好。
* 使用 Server-Sent Events (SSE) 协议,响应类型为 text/event-stream。
* 大白话解释:就像打字机一样,边想边打字,不用等全部想完才能看到内容。
* {"question": "给我讲一个200字的童话故事"}
*/
@PostMapping(value = "/stream", produces = "text/event-stream")
public Flux<String> streamChat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.getQuestion())
.stream() // 开启流式模式
.content(); // 获取 token 流
}
/**
* 4. 流式输出(带元数据)
* 如果你既想要打字机效果,又想拿到每个片段的元数据,
* 可以用 chatResponse() 替代 content()。
*/
@PostMapping(value = "/stream/detail", produces = "text/event-stream")
public Flux<ChatResponse> streamDetailChat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.getQuestion())
.stream()
.chatResponse(); // 每个 chunk 都包含元数据
}
2.Image文生图模型
yml配置
yml
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# ====================== 图像生成(文生图) ======================
image:
enabled: true
options:
model: wanx-v1
n: 1 #数量
# 图片尺寸:['1024*1024', '720*1280', '1280*720', '768*1152']
width: 1280
height: 720
style: <3d cartoon> # 风格
调用测试
java
/**
* 文生图image
*/
@RestController
@RequestMapping("/api/image")
public class ImageChatController {
@Autowired
private ImageModel imageModel;
/**
* imageModel对象会自动使用默认yml配置
* 或使用DashScopeImageModel也可以
*/
@GetMapping("/generate-image")
public String generateImage(@RequestParam String prompt) {
ImagePrompt imagePrompt = new ImagePrompt(prompt);
ImageResponse response = imageModel.call(imagePrompt);
String base64Image = response.getResult().getOutput().getB64Json(); // 或获取Base64编码
// 假设我们只取第一张图片的URL
return response.getResult().getOutput().getUrl(); // 返回图片URL,前端可以通过这个URL显示图片
}
/**
* 自定义覆盖默认配置
*/
@GetMapping("/generate-image2")
public String generateImage2(@RequestParam String prompt) {
//自定义配置
DashScopeImageOptions options = DashScopeImageOptions
.builder()
.model("qwen-image-edit-plus")
.build();
ImagePrompt imagePrompt = new ImagePrompt(prompt,options);
ImageResponse response = imageModel.call(imagePrompt);
String base64Image = response.getResult().getOutput().getB64Json(); // 或获取Base64编码
// 假设我们只取第一张图片的URL
return response.getResult().getOutput().getUrl(); // 返回图片URL,前端可以通过这个URL显示图片
}
}
3.Audio语音模型
yml配置的voice音色属性会被默认值覆盖,需要手动指定,可能是bug,优先自定义设置
yml
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# ==================== 音频配置 ====================
audio:
speech:
options:
# cosyvoice-v3-flash 是阿里云的通义千问系列模型,采用异步合成模式。
# 当你发送合成请求后,模型会立即返回一个包含音频文件URL的响应,而不是等待音频合成完成再返回数据。
# qwen3-tts-flash(同步) 因此,你需要在收到响应后,根据URL去下载音频文件。
model: cosyvoice-v3-flash # 语音合成模型 qwen3-tts-flash(同步) cosyvoice-v3-flash
# 音色 Marcus等,cosyvoice-v3-flash需要传id而不是通用名
# audio.speech.options.voice 配置项无法绑定,会丢失,永远用默认值覆盖!大坑
voice: cosyvoice-v3-flash-xxxxxxxxxxxxxxxx
format: mp3 # 音频格式: wav/mp3
speed: 1.0 # 语速 (0.5~2.0)
volume: 80 #音量
java
/**
* 阿里云百炼 TTS 语音合成 完整测试控制器
*/
@RestController
@RequestMapping("/ai/tts")
public class TtsCompleteController {
/**
* 注入 Spring AI 标准语音合成接口
* 底层实现类 = DashScopeAudioSpeechModel(阿里云百炼)
*/
@Autowired
private TextToSpeechModel textToSpeechModel;
/**
* 语音合成模型返回音频url
* @param text 要合成语音的文字
* @return url
*/
@GetMapping("/basicUrl")
public String basicUrl(@RequestParam(defaultValue = "欢迎使用阿里云百炼语音合成") String text) {
//会和yml的全局配置进行合并,优先这里的
DashScopeAudioSpeechOptions options = DashScopeAudioSpeechOptions.builder()
.model("qwen3-tts-flash") // 语音模型
.voice("Cherry") // 动态音色
.build();
// 1. 构建语音合成请求(使用 yml 里的默认配置:模型、音色、语速)
TextToSpeechPrompt prompt = new TextToSpeechPrompt(text,options);
// 2. 调用百炼接口,执行语音合成,返回响应结果
DashScopeTTSApiSpec.DashScopeAudioTTSResponse response =
(DashScopeTTSApiSpec.DashScopeAudioTTSResponse) textToSpeechModel.call(prompt);
// 获取url
return response.getOutput().audio().url();
}
/**
* 实时语音合成模型直接返回音频流(不支持同步调用)
* @param text 文本
* @return 自定义语音
*/
@GetMapping("/ttsStream")
public ResponseEntity<Resource> ttsStream(@RequestParam(defaultValue = "欢迎使用阿里云百炼语音合成") String text) {
//会和yml的全局配置进行合并,优先这里的
DashScopeAudioSpeechOptions options = DashScopeAudioSpeechOptions.builder()
//cosyvoice-v3-flash 实时模型必须传音色id,不能传通用名
.voice("longyan_v3")
.build();
TextToSpeechPrompt prompt = new TextToSpeechPrompt(text,options);
Flux<byte[]> audioFlux = textToSpeechModel.stream(prompt)
.map(res -> res.getResult().getOutput());
// 把流式音频合并成完整 MP3
byte[] fullAudio = audioFlux.collectList()
.map(chunks -> {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
chunks.forEach(baos::writeBytes);
return baos.toByteArray();
}).block();
// 返回给浏览器播放
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=tts.mp3")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new ByteArrayResource(fullAudio));
}
/**
* 流式语音合成(长文本专用,边合成边播放)
* @param text 长文本
* @return 流式音频分片(Flux)
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<byte[]> stream(@RequestParam(defaultValue = "欢迎使用阿里云百炼语音合成") String text) {
// 构建请求,yml里面的音色配置不会生效,会采用默认
TextToSpeechPrompt prompt = new TextToSpeechPrompt(text);
// 一边合成一边返回音频片段,不用等全部完成
return textToSpeechModel.stream(prompt)
.map(response ->
response.getResult().getOutput());
}
/**
* 多音色拼接接口
* 模型自动适配,全部使用 stream() 方式获取音频
*/
@GetMapping("/multi-voice")
public ResponseEntity<Resource> multiVoice() {
// ====================== 第一段语音:音色1 ======================
DashScopeAudioSpeechOptions options1 = DashScopeAudioSpeechOptions.builder()
.model("qwen3-tts-flash") // 语音模型
.voice("Cherry") // 动态音色
.build();
byte[] part1 = getAudioBytes("我是智能语音一号。", options1);
// ====================== 第二段语音:音色2 ======================
DashScopeAudioSpeechOptions options2 = DashScopeAudioSpeechOptions.builder()
.model("qwen3-tts-flash") // 语音模型
.voice("Li") // 动态音色
.build();
byte[] part2 = getAudioBytes("我是智能语音二号。", options2);
// ====================== 拼接两段音频 ======================
byte[] allAudio = new byte[part1.length + part2.length];
System.arraycopy(part1, 0, allAudio, 0, part1.length);
System.arraycopy(part2, 0, allAudio, part1.length, part2.length);
// ====================== 返回给浏览器播放 ======================
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=multi-voice.mp3")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new ByteArrayResource(allAudio));
}
/**
* 【通用工具方法】
* 从 TTS 流式接口中获取完整音频字节
*/
private byte[] getAudioBytes(String text, DashScopeAudioSpeechOptions options) {
TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);
Flux<byte[]> audioFlux = textToSpeechModel.stream(prompt)
.map(response -> response.getResult().getOutput());
// 收集所有音频分片 → 合并成完整音频
return audioFlux.collectList()
.map(chunks -> {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
chunks.forEach(baos::writeBytes);
return baos.toByteArray();
}).block();
}
}
4.Embedding向量/文本嵌入模型
把文本(句子 / 段落)转换成一段数字向量,让计算机能理解语义、做相似度匹配。常用于RAG 知识库检索,语义搜索,文档去重,推荐系统
yml配置
spring-ai-alibaba底层会默认自动配置,即使注释也会自动创建,且 enabled属性不生效
yml
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
# ==================== 文本嵌入模型配置 ====================
embedding:
# 是否启用嵌入功能(默认 true)alibaba的该配置不生效,依旧会启用
enabled: true
options:
# 嵌入模型型号
model: text-embedding-v1
dimensions: 1536 # 向量维度(固定1536,不用改)
注册向量库
java
/**
* 注册 内存向量库 Bean,重启会清空,生产环境用redis等
* 自动注入 EmbeddingModel(百炼文本嵌入模型)
*/
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
使用
java
/**
* 阿里云百炼 文本嵌入(Embedding) 测试控制器
* 功能:文本转向量、批量转向量、RAG语义检索测试
*/
@RestController
@RequestMapping("/ai/embedding")
public class EmbeddingController {
// 注入 百炼文本嵌入模型(自动根据yml配置初始化)
@Autowired
private EmbeddingModel embeddingModel;
// 注入向量库(用于RAG检索)
@Autowired
private VectorStore vectorStore;
// ====================== 1. 单文本 生成向量 ======================
@GetMapping("/single")
public List<Double> singleEmbedding(@RequestParam(value = "text", defaultValue = "我爱Spring AI")
String text) {
// 1. 调用百炼接口,将文本转为 1536 维 float[] 向量
float[] floatVector = embeddingModel.embed(text);
// 2. 把 float[] 转成 List<Double> 方便前端使用
List<Double> result = new ArrayList<>();
for (float f : floatVector) {
result.add((double) f);
}
return result;
}
// ====================== 2. 批量文本 生成向量 ======================
@GetMapping("/batch")
public List<List<Double>> batchEmbedding() {
// 1. 构造批量测试文本
List<String> texts = List.of(
"机器学习是人工智能的一个分支",
"深度学习是机器学习的子集",
"Spring AI 简化AI开发流程"
);
// 2. 批量生成向量:返回 List<float[]>
List<float[]> floatVectors = embeddingModel.embed(texts);
// 3. 转成前端友好的 List<List<Double>> 格式
List<List<Double>> result = new ArrayList<>();
for (float[] fv : floatVectors) {
List<Double> doubles = new ArrayList<>();
for (float f : fv) {
doubles.add((double) f);
}
result.add(doubles);
}
return result;
}
// ====================== 3. RAG 知识库:向量化存入向量库 ======================
@GetMapping("/addDocs")
public String addDocuments() {
// 构造知识库文档
List<Document> documents = List.of(
new Document("Spring AI Alibaba 可以对接阿里云百炼大模型"),
new Document("文本嵌入可以把文字变成向量用于语义搜索"),
new Document("RAG = 检索增强生成,让AI回答更精准")
);
// 向量化并保存到向量库
vectorStore.add(documents);
return "文档向量化入库成功!共" + documents.size() + "条";
}
// ====================== 4. RAG 语义检索(最核心功能) ======================
@GetMapping("/search")
public List<Document> search(@RequestParam(value = "query", defaultValue = "什么是RAG?") String query) {
// 1. 用户输入问题
// 2. 框架自动:问题转向量 → 向量库相似度检索 → 返回最相关的文档
return vectorStore.similaritySearch(query);
}
}