-
[Spring] Spring AI란?Spring 2025. 7. 8. 21:12
안녕하세요! 👨💻 AI 기술이 IT 생태계를 뒤흔들고 있는 요즘, 많은 Spring 개발자분들이 "어떻게 하면 우리 서비스에 AI를 자연스럽게 녹여낼 수 있을까?" 고민하고 계실 겁니다. OpenAI, Anthropic, Google AI 등 수많은 LLM이 있지만, 각기 다른 API를 배우고 연동하는 것은 꽤나 번거로운 일이죠.
오늘, 바로 그 고민을 해결해 줄 멋진 프레임워크, Spring AI에 대해 깊이 있게 알아보려 합니다.

Spring AI란 무엇일까요? 🤔
Spring AI is an application framework for AI engineering. Its goal is to apply to the AI domain Spring ecosystem design principles such as portability and modular design.
간단히 말해, Spring AI는 "Spring 스타일로 AI를 쉽고 유연하게 쓸 수 있게 해주는 도구"입니다.
Spring 개발자라면 RestTemplate이나 WebClient에 익숙하실 텐데요, 복잡한 HTTP 통신을 추상화하여 간편하게 사용하도록 도와주었죠. Spring AI는 바로 그 역할을 AI 영역에서 수행합니다. OpenAI의 GPT, Anthropic의 Claude 등 다양한 LLM을 하나의 일관된 방식으로 다룰 수 있게 해주는 강력한 추상화 계층을 제공합니다.
단순히 LLM을 호출하는 것을 넘어, 엔터프라이즈 환경에서 AI 기능을 견고하고 유연하게 통합할 수 있도록 설계된 점이 Spring AI의 진정한 가치입니다.
Spring AI, 왜 선택해야 할까요? ✨
"이미 다른 라이브러리도 많은데, 굳이 Spring AI를 써야 하나요?" 라고 물으실 수 있습니다. Spring AI가 제공하는 4가지 핵심적인 장점을 살펴보면 그 답을 찾을 수 있습니다.
1. 익숙함: Spring 개발자를 위한 손쉬운 AI 활용
Spring Boot의 가장 큰 장점은 '익숙함'과 '간편함'입니다. Spring AI도 마찬가지입니다. ChatClient, EmbeddingClient 같은 컴포넌트를 통해 AI 모델 호출을 마치 간단한 HTTP 요청처럼 처리할 수 있습니다. application.yml에 API 키와 몇 가지 설정을 추가하면 준비는 끝납니다. 진입 장벽이 매우 낮죠.
2. 유연성: 놀랍도록 자유로운 모델 교체
처음에는 OpenAI의 GPT-4 모델로 개발을 시작했다고 가정해봅시다. 그런데 비용이나 성능상의 이유로 Anthropic의 Claude 모델로 변경해야 한다면 어떨까요? Spring AI를 사용하지 않았다면 벤더의 SDK를 교체하고 API 호출 코드를 전부 수정해야 했을 겁니다.
하지만 Spring AI는 코드를 거의 수정하지 않고 설정 변경만으로 모델을 교체할 수 있습니다. 이것이 가능한 이유는 다양한 모델을 일관된 방식으로 요청할 수 있도록 추상화했기 때문입니다.
3. 확장성: RAG 패턴과 벡터 스토어의 간편한 통합
LLM의 한계를 보완하는 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 패턴이 요즘 대세입니다. 이 패턴을 구현하려면 문서를 나누고(chunking), 벡터로 만들고(vectorizing), 벡터 DB에 저장하는 복잡한 과정이 필요한데요, Spring AI는 이 흐름에 필요한 기능들을 기본 제공합니다.
처음에는 InMemoryVectorStore로 가볍게 시작했다가, 서비스가 성장하면 PGVector나 Qdrant 같은 전문 벡터 DB로 손쉽게 확장할 수 있습니다. 추상화의 힘 덕분이죠.
4. 최신 기능: Function Calling 등 핵심 기능 내장
LLM의 답변을 기반으로 특정 함수를 호출하는 Function Calling이나, 동적으로 프롬프트를 구성하는 Prompt Template 같은 최신 AI 기술들을 프레임워크 차원에서 지원합니다. 개발자가 복잡한 로직을 직접 구현하지 않고도 안정적인 기반 위에서 빠르게 서비스를 만들어나갈 수 있습니다.
핵심 추상화와 구조 파헤치기 🏗️
그렇다면 Spring AI는 구체적으로 어떻게 이런 장점들을 제공할까요? 핵심적인 추상화 계층 2가지를 살펴보겠습니다.
1. Prompt와 ChatOptions: 요청을 섬세하게 제어하기
Prompt 객체는 LLM에 보낼 요청을 감싸는 역할을 합니다. 여기에는 '어떤 메시지를 보낼지'(List<Message>) 와 '어떤 옵션으로 보낼지'(ChatOptions) 가 함께 담깁니다.
public class Prompt implements ModelRequest<List<Message>> { private final List<Message> messages; private ChatOptions modelOptions; @Override public ChatOptions getOptions() {...} @Override public List<Message> getInstructions() {...} }여기서 주목할 것은 ChatOptions 인터페이스입니다.
public interface ChatOptions extends ModelOptions { String getModel(); Float getFrequencyPenalty(); // frequencyPenalty Integer getMaxTokens(); // maxTokens Float getPresencePenalty(); // presencePenalty List<String> getStopSequences(); // stopSequences Float getTemperature(); // temperature Integer getTopK(); // topK Float getTopP(); // topP ChatOptions copy(); }temperature, maxTokens 등 대부분의 LLM이 공통으로 사용하는 옵션들을 정의합니다. 가장 놀라운 점은 벤더별로 다른 파라미터 이름을 Spring AI가 알아서 변환해준다는 것입니다. 예를 들어, 개발자는 stopSequences라는 공통 옵션을 사용하면, OpenAI의 stop 파라미터와 Anthropic의 stop_sequences 파라미터로 자동 매핑됩니다.
2. ChatModel: 다양한 LLM을 아우르는 중심축
ChatModel은 Spring AI의 심장과 같은 핵심 인터페이스입니다. LLM과의 모든 상호작용은 이 인터페이스를 통해 이루어집니다.
public interface ChatModel extends Model<Prompt, ChatResponse> { // 기본 구현을 제공하는 default 메서드 (Java 8 이상) default String call(String message) { // 예시 구현. 필요에 따라 수정 Prompt prompt = new Prompt(message); ChatResponse response = call(prompt); return response.getContent(); // 혹은 원하는 형태로 변환 } @Override ChatResponse call(Prompt prompt); }call 메서드는 Prompt를 받아 ChatResponse를 반환합니다. 이 ChatResponse 안에는 모델의 응답 메시지뿐만 아니라 토큰 사용량, 응답 시간 등 유용한 메타 정보가 포함되어 있어 로깅이나 디버깅에 매우 유용합니다.
내부적으로 ChatModel 구현체는 다음과 같은 순서로 동작합니다.
- 입력 변환: Prompt 객체를 각 벤더(OpenAI, Anthropic 등)의 API 명세에 맞는 요청 형식으로 변환합니다.
- API 호출: 변환된 요청으로 실제 벤더의 API를 호출합니다.
- 결과 변환: 벤더로부터 받은 응답을 Spring AI의 표준 응답 객체인 ChatResponse로 다시 변환하여 반환합니다.

Spring AI 동작과정 이러한 추상화 덕분에 개발자는 벤더의 차이를 신경 쓰지 않고 ChatModel 인터페이스에만 집중할 수 있습니다.
코드로 직접 살펴보기 💻
백문이 불여일견이죠. OpenAI와 Anthropic 모델을 각각 호출하는 ChatService 예제 코드를 통해 Spring AI의 실제 사용법을 확인해 보겠습니다.
import org.springframework.ai.anthropic.AnthropicChatModel; import org.springframework.ai.anthropic.api.AnthropicApi; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; @Service public class ChatService { private final OpenAiApi openAiApi; private final AnthropicApi anthropicApi; public ChatService(OpenAiApi openAiApi, AnthropicApi anthropicApi) { this.openAiApi = openAiApi; this.anthropicApi = anthropicApi; } public ChatResponse getOpenAiResponse(String userInput, List<String> stop, double temperature) { List<Object> messages = Arrays.asList( new SystemMessage("You are a helpful assistant."), new UserMessage(userInput) ); ChatOptions chatOptions = ChatOptions.builder() .model("gpt-3.5-turbo") .temperature(temperature) .stopSequences(stop) .build(); Prompt prompt = new Prompt(messages, chatOptions); OpenAiChatModel chatModel = OpenAiChatModel.builder() .openAiApi(openAiApi) .build(); return chatModel.call(prompt); } public ChatResponse getAnthropicResponse(String userInput, List<String> stop, double temperature) { List<Object> messages = Arrays.asList( new SystemMessage("You are a helpful assistant."), new UserMessage(userInput) ); ChatOptions chatOptions = ChatOptions.builder() .model("claude-3-7-sonnet-20250219") .temperature(temperature) .stopSequences(stop) .build(); Prompt prompt = new Prompt(messages, chatOptions); AnthropicChatModel chatModel = AnthropicChatModel.builder() .anthropicApi(anthropicApi) .build(); return chatModel.call(prompt); } }import org.springframework.ai.anthropic.api.AnthropicApi; import org.springframework.ai.openai.api.OpenAiApi; import java.util.Arrays; public class ChatMain { public static void main(String[] args) { String openAiApiKey = System.getenv("OPENAI_API_KEY"); String anthropicApiKey = System.getenv("ANTHROPIC_API_KEY"); OpenAiApi openAiApi = OpenAiApi.builder() .apiKey(openAiApiKey) .build(); AnthropicApi anthropicApi = AnthropicApi.builder() .apiKey(anthropicApiKey) .build(); ChatService chatService = new ChatService(openAiApi, anthropicApi); var openAiResponse = chatService.getOpenAiResponse( "Tell me a joke.", Arrays.asList("\n", "END"), 0.5 ); System.out.println("OpenAI Response: " + openAiResponse); var anthropicResponse = chatService.getAnthropicResponse( "Tell me a joke.", Arrays.asList("\n", "END"), 0.5 ); System.out.println("Anthropic Response: " + anthropicResponse); } }어떤가요? getOpenAiResponse와 getAnthropicResponse 두 메서드의 로직이 거의 똑같다는 것을 알 수 있습니다. 메시지와 옵션을 담아 Prompt를 만들고, ChatModel의 call 메서드를 호출하는 핵심 로직은 동일합니다. 단지 OpenAiChatModel을 쓸지 AnthropicChatModel을 쓸지만 다를 뿐입니다. 이것이 바로 Spring AI가 추구하는 '이식성'과 '모듈화'의 힘입니다.
마무리하며
Spring AI는 단순히 또 하나의 AI 라이브러리가 아닙니다. 수십 년간 엔터프라이즈 애플리케이션 개발을 선도해 온 Spring의 철학을 AI 엔지니어링에 접목한 결과물입니다.
익숙한 개발 방식으로, 유연하고 확장 가능하며, 최신 기술을 손쉽게 적용할 수 있다는 점에서 Spring AI는 모든 Spring 개발자에게 강력한 무기가 될 것입니다. 이제 여러분의 서비스에 AI 날개를 달아줄 시간입니다. 지금 바로 Spring AI를 시작해보세요!
'Spring' 카테고리의 다른 글
[Java/Spring] DTO: 데이터 전송 객체 (0) 2025.12.12 [Java/Spring] VO(Value Object)란 무엇인가? (0) 2025.12.11 스프링(Spring)이란 무엇인가? (1) 2025.06.18 스프링(Spring)의 역사와 탄생 배경 (2) 2025.06.18 [Spring Boot]서비스 코드를 Interface와 Class로 나누는 이유 (0) 2025.04.15