2026最新Spring AI+多智能体版天机学堂——基于OpenAI实现语言文字交互接口
公开项目接口:
Apifox Hub:

查看OpenAI接口参数(语音、转录、翻译):
可以看到,文字转语音的接口,提交参数是通过body方式提交的,是需要待转换的文字内容。



文字转语音:
引入OpenAI
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
改造SpringAI配置类:
@Bean
public ChatClient chatClient(@Qualifier("dashscopeChatModel") ChatModel dashScopeChatModel, //注意这里@Qualifier的指定的名称,不要写错了
Advisor loggerAdvisor, // 日志记录器
Advisor messageChatMemoryAdvisor,
Advisor recordOptimizationAdvisor // 记录优化
// CourseTools courseTools, // 课程工具
// OrderTools orderTools // 预下单工具
) {
return ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(loggerAdvisor, messageChatMemoryAdvisor, recordOptimizationAdvisor) //添加 Advisor 功能增强
// .defaultTools(courseTools, orderTools)
.build();
}
@Bean
public ChatClient openAiChatClient(@Qualifier("openAiChatModel") ChatModel openAiChatModel,
Advisor loggerAdvisor // 日志记录器
) {
return ChatClient.builder(openAiChatModel)
.defaultAdvisors(loggerAdvisor)
.build();
}
注意:
1. 配置文件决定"有哪些模型"
配置文件(application.yml)提供了 API key、URL 等参数,Spring Boot 的自动配置据此创建 ChatModel Bean:
spring:
ai:
dashscope:
api-key: xxx # → 创建名为 dashscopeChatModel 的 Bean
openai:
api-key: xxx # → 创建名为 openAiChatModel 的 Bean2. 代码中的 @Qualifier 决定"用哪个模型"
配置文件只负责创建两个 Bean。但你在代码里写 @Qualifier("dashscopeChatModel") 还是
@Qualifier("openAiChatModel"),决定了注入给谁。所以:
- 配置文件 = 工厂,生产了两个模型实例
- @Qualifier = 你指定拿哪个
- 调用时 chatClient(...) 或 openAiChatClient(...) = 你选的这条路决定了最终用哪个模型就是在注入时,注入那个bean对象,则调用哪个模型。
//看bean名称
private final ChatClient chatClient;//注入阿里云
private final ChatClient openAiChatClient;//注入openAI
tts配置:
spring:
ai:
openai:
audio:
speech:
base-url: https://api.chatanywhere.tech/
api-key: ${tj.ai.openai.key}
options:
model: tts-1 #可用的 TTS 模型之一:tts-1 或 tts-1-hd
voice: alloy #生成音频时使用的语音。支持的语音有:alloy、echo、fable、onyx、nova 和 shimmer。
response-format: mp3 #默认为 mp3 音频的格式。支持的格式有:mp3、opus、aac 和 flac。
speed: 1.0 #默认为 1 生成的音频速度。选择0.25到4.0之间的值。1.0是默认值。
Controller层:
@RestController
@RequestMapping("/audio")
@RequiredArgsConstructor
public class AudioController {
private final AudioService audioService;
@NoWrapper//返回结果不需要包装
@PostMapping(value = "/tts-stream", produces = "audio/mp3")//value->路径值
//响应头 Content-Type: audio/mp3,适合音频流
//不加 produces 默认可能为 application/json(Spring 会尝试转换 ResponseBodyEmitter 导致异常)
public ResponseBodyEmitter ttsStream(@RequestBody String text) {
return this.audioService.ttsStream(text);
}
}
ServiceImpl层:
@Service
@Slf4j
@RequiredArgsConstructor
public class AudioServiceImpl implements AudioService {
private final OpenAiAudioSpeechModel openAiAudioSpeechModel;
@Override
public ResponseBodyEmitter ttsStream(String text) {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
log.info("开始语音合成, 文本内容:{}", text);
SpeechPrompt speechPrompt = new SpeechPrompt(text);
Flux<SpeechResponse> responseStream = openAiAudioSpeechModel.stream(speechPrompt);
// 订阅响应流并发送数据
responseStream.subscribe(
speechResponse -> {
try {
// 获取响应输出的数据,并发送到响应体中
byte[] audioBytes = speechResponse.getResult().getOutput();
//发送给前端
emitter.send(audioBytes);
} catch (IOException e) {
emitter.completeWithError(e);
}
},
emitter::completeWithError,
emitter::complete
);
return emitter;
}
}
语音转文字:
引入依赖:
在输出的结果中,可能会存在繁体中文,所以需要将繁体转化为简体中文,用到了opencc4j组件,所以需要导入其依赖:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>opencc4j</artifactId>
<version>1.8.0</version>
</dependency>
stt配置:
基于接口来定的:
spring:
ai:
openai:
audio:
transcription:
base-url: https://api.chatanywhere.tech/
api-key: ${tj.ai.openai.key}
options:
model: whisper-1 #要使用的模型 ID。目前只有 whisper-1 是可用的。
response-format: text #转录输出的格式,可选择:json、text、srt、verbose_json 或 vtt。
temperature: 0 #默认为 0,采样温度,between 0 和 1。更高的值像 0.8 会使输出更随机,而更低的值像 0.2 会使其更集中和确定性。如果设置为 0,模型将使用对数概率自动增加温度直到达到特定阈值。
language: zh #输入音频的语言。以 ISO-639-1 格式提供输入语言可以提高准确性和延迟。
Controller层:
@PostMapping("/stt")
public String stt(@RequestParam("audioFile") MultipartFile audioFile) {
return this.audioService.stt(audioFile);
}
ServiceImpl层:
private final OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel;
@Override
public String stt(MultipartFile multipartFile) {
// 将MultipartFile转换为Resource
Resource audioResource = multipartFile.getResource();
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioResource);
// 调用OpenAiAudioTranscriptionModel进行语音识别
AudioTranscriptionResponse response = openAiAudioTranscriptionModel.call(transcriptionRequest);
// 获取识别结果
String output = response.getResult().getOutput();
// 将繁体转换为简体
return ZhConverterUtil.toSimple(output);//转为中文
}
Bug修复:
自定义了
WrapperResponseMessageConverter消息转化器,他的作用是对输出的内容进行包装,而SpringAI底层用的发起http请求的组件是RetryTemplate,而RetryTemplate也会用到这个消息转化器,但是这个消息转化器是无法处理文件的,所以报消息转化出错:
给RetryTemplate添加一个监听器,在发起Springai模块请求前设置标识,请求结束后删除标识,这样就可以解决问题了。如果是Springai请求,则跳过自定义WrapperResponseMessageConverter消息转化器,这样就不会出错了。
/**
* 创建并配置自定义重试监听器Bean
* <p>
* 实现说明:
* 1. 创建匿名RetryListener实现,在重试操作期间管理Web属性
* 2. 将监听器注册到提供的RetryTemplate实例
*
* @param retryTemplate Spring Retry模板对象,用于注册重试监听器
* @return RetryListener 已注册到模板的重试监听器实例,将由Spring容器管理
*/
@Bean
public RetryListener customizeRetryTemplate(RetryTemplate retryTemplate) {
// 创建自定义重试监听器,实现以下核心功能:
// - 重试开始时设置上下文标识
// - 重试结束后清理上下文标识
RetryListener retryListener = new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
WebUtils.setAttribute(Constant.SPRING_AI_ATTR, Constant.SPRING_AI_FLAG);
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
WebUtils.removeAttribute(Constant.SPRING_AI_ATTR);
}
};
// 将监听器注册到重试模板
retryTemplate.registerListener(retryListener);
return retryListener;
}更多推荐



所有评论(0)