음성도 두 방향이 있다 — 말을 글로 받아쓰는 STT(Speech-To-Text) 와, 글을 말로 읽어 주는 TTS(Text-To-Speech) 다. 둘을 합치면 음성 비서처럼 "듣고 → 생각하고 → 말하는" 흐름을 만들 수 있다.
이 편에서 만드는 것
/audio/tts(텍스트 → mp3) · /audio/stt(음성 파일 → 텍스트) 두 개의 엔드포인트.
flowchart LR
T["텍스트"] -->|TTS · tts-1| V1["음성(mp3)"]
V2["음성 파일"] -->|STT · whisper-1| T2["텍스트"]1. TTS — 텍스트를 음성으로
OpenAiAudioSpeechModel에 텍스트와 옵션을 넘기면 음성 바이트(mp3)를 돌려준다. 목소리(voice)·형식(responseFormat)·속도(speed)를 지정한다.
public byte[] textToSpeech(String text) {
OpenAiAudioSpeechOptions options = OpenAiAudioSpeechOptions.builder()
.model("tts-1")
.voice(SpeechRequest.Voice.ALLOY) // 목소리 종류
.responseFormat(AudioResponseFormat.MP3) // 출력 형식
.speed(1.0) // 말하기 속도
.build();
return speechModel.call(new TextToSpeechPrompt(text, options))
.getResult().getOutput(); // mp3 byte[]
}@GetMapping(value = "/audio/tts", produces = "audio/mpeg")
public byte[] tts(@RequestParam String text) {
return audioService.textToSpeech(text);
}curl -G "http://localhost:8080/audio/tts" \
--data-urlencode "text=안녕하세요. 스프링 AI 음성 합성 테스트입니다." \
-o tts.mp3
# 재생 가능한 mp3 저장Spring AI 1.1의 TTS API
1.1 버전에서 TTS는 공통 추상화 TextToSpeechModel / TextToSpeechPrompt(org.springframework.ai.audio.tts)로 정리됐다. 예전 자료의 SpeechPrompt(...openai.audio.speech)는 더 이상 없으니 TextToSpeechPrompt를 쓴다.
2. STT — 음성을 텍스트로
OpenAiAudioTranscriptionModel에 음성 파일(Resource)을 넘기면 받아쓴 텍스트를 돌려준다. whisper-1 모델을 쓰고, 언어를 한국어(ko)로 지정한다.
public String speechToText(Resource audio) {
OpenAiAudioTranscriptionOptions options = OpenAiAudioTranscriptionOptions.builder()
.model("whisper-1")
.language("ko") // 인식 언어
.responseFormat(TranscriptResponseFormat.TEXT)
.build();
AudioTranscriptionResponse response =
transcriptionModel.call(new AudioTranscriptionPrompt(audio, options));
return response.getResult().getOutput();
}@PostMapping(value = "/audio/stt", produces = MediaType.TEXT_PLAIN_VALUE)
public String stt(@RequestParam("file") MultipartFile file) {
return audioService.speechToText(file.getResource()); // 파일명·형식 정보 포함
}curl -X POST -F "file=@tts.mp3;type=audio/mpeg" http://localhost:8080/audio/stt
# 안녕하세요, 스프링 AI 음성 합성 테스트입니다.MultipartFile.getResource()를 쓰는 이유
Whisper API는 오디오 형식을 파일 확장자로 판별한다. MultipartFile.getResource()는 원본 파일명을 보존하므로 형식 인식이 잘 된다. (단순 ByteArrayResource는 파일명이 없어 형식 추론에 실패할 수 있다.)
3. 합치기 — 음성 비서 흐름
STT → LLM → TTS를 이으면 "음성으로 묻고 음성으로 답하는" 비서가 된다.
flowchart LR
V["사용자 음성"] -->|STT| Q["질문 텍스트"]
Q --> LLM
LLM --> A["답변 텍스트"]
A -->|TTS| VA["답변 음성"]public byte[] voiceChat(Resource voice) {
String question = speechToText(voice); // ① 듣기
String answer = chatClient.prompt() // ② 생각하기
.user(question).call().content();
return textToSpeech(answer); // ③ 말하기
}정리
방향 | 모델 | 핵심 API |
|---|---|---|
TTS (텍스트→음성) | tts-1 |
|
STT (음성→텍스트) | whisper-1 |
|
여기까지 채팅·프롬프트·구조화 출력·Tool Calling·RAG·멀티모달을 다뤘다. 각 기능은 독립적이지만, 실제 서비스는 이들을 조합해 만든다 — RAG로 근거를 찾고, Tool Calling으로 실행하고, 멀티모달로 보고 듣는 식이다. 이어지는 두 편에서는 대화를 기억하게 하는 Chat Memory와, 호출을 가로채 공통 기능을 끼우는 Advisor를 다룬다.
➡️ 다음 편: Chat Memory