AI 애플리케이션을 개발할 때,
어떤 방식으로 모델에 입력 데이터를 전달하고, 생성된 결과를 사용자에게 보여줄 것인지는 많은 개발자들이 공통으로 고민하는 부분이다.
파이썬 기반의 AI 애플리케이션에서는 보통 LangChain(랭체인) 같은 프레임워크를 사용한다.
LangChain은 프롬프트 구성 → 모델 호출 → 후처리 과정이 하나의 흐름(Chain)으로 자연스럽게 이어지도록 설계되어 있어, 복잡한 로직을 단계적으로 연결할 수 있다는 장점이 있다.
스프링 AI(SPRING AI) 역시 내부 구현 언어나 실행 환경은 다르지만,
제공하는 기능의 방향성과 개발 경험은 LangChain과 거의 유사하다.
스프링 AI는 OpenAI, HuggingFace 등 다양한 LLM 모델을 자동으로 연결해주고,
엔터프라이즈 환경에서 자주 사용하는 벡터 스토어(Vector Store) 와의 연동을 기본 지원한다.
또한 대화 기억 관리 방식 선택, RAG 구현, 도구 호출, MCP 서버 개발 등
AI 서비스를 구축할 때 필요한 기능들을 폭넓게 제공한다.
파이썬이 익숙한 개발자라면 굳이 기존 파이썬 애플리케이션을 스프링으로 옮길 필요는 없다.
하지만 자바·스프링 생태계에서 개발해 온 사람들에게는
스프링 AI의 등장이 매우 반가운 변화임은 분명하다.
AI 모델의 종류는 지금 이 순간에도 빠르게 늘어나고 있다.
우리는 ChatGPT를 사용할 수도 있고, Google Gemini나 Anthropic Claude 를 선택할 수도 있다.
하지만 문제는 각 AI 제공업체마다 API 호출 방식과 설정 코드가 모두 다르다는 것이다.
서비스를 만들 때 여러 모델을 함께 쓰거나 다른 모델로 갈아타야 한다면,
매번 새로운 SDK 문서를 다시 읽고 코드 구조를 수정해야 하는 불편이 생긴다.
Spring AI는 이러한 문제를 해결하기 위해
같은 종류의 모델들은 동일한 방식으로 사용할 수 있도록 통합된 API를 제공한다.
또한 Spring의 철학답게,
모델 호출을 대부분 인터페이스 기반으로 설계해 두어
AI 공급자를 변경하더라도 코드 전체를 고칠 필요는 없다.
최소한의 구성만 수정하면 다른 모델로 자연스럽게 교체가 가능하다.
| API | 기능 |
|---|---|
| ChatModel | 텍스트·이미지 입력 → 텍스트 생성 |
| ImageModel | 텍스트 → 이미지 생성 |
| SpeechModel | 텍스트 ↔ 음성 변환 |
| EmbeddingModel | 텍스트 → 벡터 변환 |
ChatModel은 StreamChatModel을 상속받는 인터페이스다.
텍스트나 이미지를 입력받아 텍스트를 생성한다.
ChatModel은 메시지를 처리하는 추상화된 대화 엔진 이다.

Message API는 ChatModel이 대화를 표준화된 방식으로 처리할 수 있도록 제공되는
통합 메시지 데이터 구조이다.
메세지란 LLM에게 전달할 대화 단위 객체로
단순한 문자열이 아닌 “역할(role) + 내용(content)” 구조로 메시지를 정의하는 방식을 말한다.
ChatModel은 입력으로 List<Message> 를 받으며
(SystemMessage, UserMessage, AssistantMessage, ToolMessage …)
이 메시지들을 하나의 대화 컨텍스트로 조합한 뒤 LLM에 요청을 전송한다.
모델의 응답은 AssistantMessage 또는 메시지 스트림(Streaming Response) 형태로 반환된다.

LLM과 주고받는 모든 메시지는 공통적으로
Message 인터페이스를 구현해 일관된 형태로 구성되며,
이미지·음성·파일 등의 멀티모달 입력이 필요할 경우
추가적으로 MediaContext 를 함께 구현하여 전달할 수 있다.
| 클래스 | 역할(role) | 설명 |
|---|---|---|
| SystemMessage | system | 모델에게 행동 지침, 규칙, 컨텍스트를 제공하는 설정 메시지. |
| UserMessage | user | 사용자 입력. 질문, 요청, 명령 등 사람이 작성한 모든 내용. |
| AssistantMessage | assistant | 모델이 생성한 응답. 텍스트, 코드, 설명 등. |
| FunctionMessage | function | 함수 호출 기능 사용 시, 함수 실행 결과를 모델에게 다시 전달하는 용도. |
| ToolMessage | tool | 외부 도구 실행 결과를 전달하는 메시지. |
package com.example.demo.controller;
import java.text.MessageFormat;
import java.util.List;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.demo.model.Product;
@Controller
public class MarketingController {
@Autowired
private OpenAiChatModel chatModel;
@GetMapping("/marketing")
public String getMarketing() {
return "marketing_request";
}
@PostMapping("/marketing")
public String postMarketing(@ModelAttribute Product product, Model model) {
// ===== 시스템 메세지 =====
var systemMessage = new SystemMessage("""
너는 전문 마케팅 카피라이터야.
입력된 제품 정보를 기반으로 매력적인 광고 문구를 만들어줘.
표현은 자연스럽고 구매욕을 높이는 방식으로 작성해줘.
""");
// ===== 사용자 메세지 =====
var userMessage = new UserMessage(MessageFormat.format("""
아래 제품 정보를 기반으로 마케팅 카피를 생성해줘.
- 제품명 : {0}
- 가격 : {1}
- 구매링크 : {2}
- 제품의 특징 : {3}
""",
product.getName(),
product.getPrice(),
product.getLink(),
product.getFeatures()
));
// ===== 옵션 설정 (2000토큰, temperature 0.7) =====
ChatOptions options = ChatOptions.builder()
.maxTokens(2000)
.temperature(0.7)
.build();
// ===== Prompt 생성 =====
Prompt prompt = new Prompt(
List.of(systemMessage, userMessage),
options
);
// ===== 모델 호출 =====
AssistantMessage result = chatModel.call(prompt).getResult().getOutput();
model.addAttribute("marketingResult", result);
return "marketing_response";
}
}


Spring AI에서 프롬프트는 LLM에 사용자가 원하는 작업을 구체적으로 지시하는 명령문으로
이를 구조화한 객체가 Prompt 클래스다.
프롬프트 안에는 모델의 행동을 조절하는 대화 옵션(ChatOptions)이 포함되어 있고, 필요하면 프롬프트 템플릿을 통해 매개 변수를 동적으로 넣거나 이전 대화 내용을 함께 전달해 더 자연스러운 응답을 만들 수도 있다.
프롬프트를 설계할 때는 모델에게 맡길 역할을 명확히 정해주고, 요청의 목적이 한눈에 들어오도록 직관적인 문장으로 지시하는 것이 핵심이다. 이렇게 잘 다듬어진 프롬프트는 Prompt → ChatModel → 모델 응답의 흐름을 거치며 처리되고, 그 결과 모델은 사용자의 의도에 훨씬 더 가까운 답변을 만들 것이다.
| 종류 | 설명 | 대표 사용 예 |
|---|---|---|
| Prompt | 모델에 전달되는 기본 요청 객체. 단일 텍스트 또는 메시지 리스트 입력 | "Hello?" |
| PromptTemplate | 프롬프트 내부에 변수를 바인딩하는 템플릿 | "오늘 {city}의 날씨는?" |
| SystemPrompt | 시스템(Role=system) 지침을 템플릿 형태로 구성 | "너는 여행 가이드야" |
| UserPrompt | 사용자(User) 메시지를 템플릿으로 구성 | "서울 맛집 추천해줘" |
| ChatPrompt | 여러 Role(System/User/Assistant) 기반의 대화형 프롬프트 | AI 대화 히스토리 포함 |
| PromptTemplateResource | .st 등 외부 템플릿 파일을 불러와 사용하는 프롬프트 | classpath:/prompts/summary.st |
Prompt와 함께 사용되는 옵션 객체는,
모델 종류(OpenAI, Gemini, Claude 등)에 관계없이 적용할 수 있는 공통 규격의 표준 옵션을 제공한다.
물론 각 업체 모델마다 지원하는 추가 옵션들이 더 있지만,
이 표준 옵션만으로도 출력 길이 조절, 창의성 제어, 모델 선택 등 대부분의 핵심 기능을 충분히 다룰 수 있다.
package org.springframework.ai.model;
public interface ChatOptions extends ModelOptions {
// 응답 최대 길이 (토큰)
Integer getMaxTokens();
// 출력 창의 다양성(창의성). 0.0 ~ 1.0
Double getTemperature();
// Top-p nucleus sampling
Double getTopP();
// 사용할 모델 (gpt-4o, gemini-pro 등)
String getModel();
// 스트리밍 여부
Boolean getStream();
// stop sequences
List<String> getStopSequences();
// 반복 방지(repetition penalty)
Double getRepetitionPenalty();
}
| 옵션 | 설명 |
|---|---|
| maxTokens | 모델이 생성할 최대 토큰 수 (출력 길이 제한) |
| temperature | 창의성·랜덤성 조절 (0은 결정적, 1은 창의적) |
| topP | nucleus sampling. 상위 확률 p% 안에서만 샘플링 |
| model | 사용할 LLM 모델 ID. ex) "gpt-4o", "gemini-pro" |
| stream | 스트리밍 응답 사용 여부 |
| stopSequences | 생성 중단할 문자열 패턴들 |
| repetitionPenalty | 반복 생성 억제 |
[SystemMessage] ┐
[UserMessage] │ ==(List<Message>)==> Prompt + ChatOptions -> ChatModel
[AssistantMessage] │
[ToolMessage] ┘
Prompt:
- messages
- chatOptions (maxTokens, temperature, model ...)
기본 프로젝트 세팅 : spring AI 연동 & 세팅 방법
도서 - 이것이 스프링AI다
공식문서 - 스프링 AI