Spring AI

멀티모달 - 음성

음성도 두 방향이 있다 — 말을 글로 받아쓰는 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

speechModel.call(new TextToSpeechPrompt(text, options))

STT (음성→텍스트)

whisper-1

transcriptionModel.call(new AudioTranscriptionPrompt(resource, options))

여기까지 채팅·프롬프트·구조화 출력·Tool Calling·RAG·멀티모달을 다뤘다. 각 기능은 독립적이지만, 실제 서비스는 이들을 조합해 만든다 — RAG로 근거를 찾고, Tool Calling으로 실행하고, 멀티모달로 보고 듣는 식이다. 이어지는 두 편에서는 대화를 기억하게 하는 Chat Memory와, 호출을 가로채 공통 기능을 끼우는 Advisor를 다룬다.

➡️ 다음 편: Chat Memory

마지막 수정 2026년 10월 22일