2. Spring AI 활용 - 해당 코스를 수강하고 싶은 이유
실무 적용 및 업무 효율화
현 시스템 개선
기존 Spring 기반의 시스템(예: 예약 서비스,
모니터링, 챗봇 등)에 AI 기능을 통합해 운영
자동화, 유지보수 용이성 및 생산성 향상을
기대하고 있음
실질적 효과 추구
실무에서 바로 활용할 수 있는 AI 도구 개발 및
서비스 개선을 목표로 함
실습 중심 학습 및 지식 공유
실제 구현 경험 중시
이론보다는 실습과 직접 구현하는 과정을 통해
즉각적으로 업무에 적용 가능한 경험을 얻고자 함
내부 전파 기대
배운 내용을 팀과 조직 내에서 공유하며, 조직
전체의 AI 활용 역량을 강화하려는 목표
익숙한 Spring 생태계 내에서 최신 AI 기술을 실습 및 적용함으로써 개발 생산성과 실무 역량을 크게 향상시키고자 하는 목표
Spring 생태계와 기술 시너지
익숙한 환경 활용
Java/Kotlin 및 Spring 프레임워크에 이미
익숙한 개발자들이, Python 기반의 AI
솔루션보다 친숙한 환경에서 AI 기능을
구현하고자 함
최신 AI 기술 습득
LLM, RAG, 챗봇 등 최신 AI 기술을 Spring
환경에 접목하여 보다 안정적이고 유지보수
가능한 시스템을 만들려는 의지가 강함
2
3. 생성형 AI 개념
이해하기
Model
Prompt
Prompt Template
Token
Message
Structured Output
Streaming
Embedding
Vector DB
Function Calling
Evaluation
Reasoning
Agent
Spring AI 실습 커리큘럼
생성형 AI에 대한 개념에 대한 숙지와 함께 Spring AI 로 간단한 챗봇부터 RAG, Tools 사용, 추론 모델 활용, Agent 까지 경험합니다.
1 2 3 4 5
Spring AI
여행도우미 챗봇
Spring AI 소개
OpenAI API 연결
프로젝트 환경설정
Message 작성
Template 적용
코드와 Prompt 분리
Prompt 엔지니링
Streaming 처리
버퍼링
ChatBot UI 연결
채팅 히스토리 관리
RAG와 Tools로
응답 고도화
Spring AI Advisor
Vector Store 연결
임베딩 모델 적용
Vector 유사도 검색
Tools 적용
MCP 이해
외부 API 연결
ETL 파이프라인
추론 모델을
활용한 기능 추가
최적의 여행 일정
추론기반 여행지 추천
가설의 플랜 B, C
의사 결정 지원
쿼리 확장기
Multimodality
Agent 개념을
적용하기
Workflow와 Agent
Chain Workflow
Parallelization
Routing
Orchestrator
Evaluator
AI Agent Patterns
3
4. 생성형 AI 개념 이해하기 (1) - Model
텍스트, 이미지, 오디오 등을 처리하기 위한 다양한 유형의 AI 모델이 존재
4
5. 생성형 AI 개념 이해하기 (2) - Prompt
AI 모델이 특정 출력을 생성하도록 지시하는 도구로 프롬프트를 사용
고양이 이미지를 생성해줘 나무로 둘러싸인 정원에서 느긋하게 휴식을 취하고 있는 파란
눈이 인상적인 푹신한 흰색 고양이의 사진을 생성해줘
이미지 생성 : Google Image FX
5
6. 생성형 AI 개념 이해하기 (3) - Prompt Template
프롬프트 템플릿을 통해 하나의 프롬프트로 다양한 결과물을 도출 : {장소} 에서 {상태} {대상}의 사진을 생성해줘
{붐비는 도시 광장}에서 {즐겁게 공연하는} {거리 공연자}의 사진을 생성해줘.
이미지 생성 : Google Image FX
{안개 낀 숲}에서 {신비로운} {늑대}의 사진을 생성해줘.
{햇살 가득한 해변}에서 {느긋하게 쉬고 있는} {바다거북}의 사진을 생성해줘. {아늑한 도서관}에서 {집중해서 책을 읽고 있는} {학자}의 사진을 생성해줘.
6
7. 실습 몸풀기 (1) - Spring AI 로 OpenAI 연결
Spring AI를 프로젝트를 생성하고 OpenAI 모델에 프롬프트를 보내 결과 받기
1. Spring initializer 를 통해 프로젝트를 생성해주세요.
- https://start.spring.io
- Options : Gradle - Kotlin / Kotlin / Spring Boot v3.4.3 / Jar Package / Java v21
- Dependencies :
- Spring Web
- Spring Reactive Web
- OpenAI
- Spring Boot DevTools
2. OpenAI API Key 등록
- resources/application.properties에 api key 등록
spring.ai.openai.api-key={api_key}
// 설정파일은 properties 대신 yaml 를 추천합니다
// api key 는 환경 변수 또는 vault에 등록하여 사용해주세요
3. build.gradle.kts 파일 확인
7
8. 실습 몸풀기 (1) - Spring AI 로 OpenAI 연결
Spring AI를 프로젝트를 생성하고 OpenAI 모델에 프롬프트를 보내 결과 받기
4. Controller 추가
@RestController
@RequestMapping("/api/chat")
class ChatController {
@RequestMapping("/hello")
fun hello(): String {
return "Hello, Spring AI!"
}
}
@RestController
@RequestMapping("/api/chat")
class ChatController(
chatModel: OpenAiChatModel
) {
private val chatClient = ChatClient.builder(chatModel).build()
@GetMapping
fun hello(@RequestParam query: String): String {
return chatClient.prompt()
.user(query)
.call()
.content()!!
}
}
5. OpenAI ChatClient 를 사용하여 AI 응답 결과를 받도록 합니다
6. 프로젝트 실행 후
브라우저에서 결과 확인
ERROR 62236 --- [spring-ai-start] [ restartedMain]
i.n.r.d.DnsServerAddressStreamProviders : Unable to load
io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamPro
vider, fallback to system defaults. This may result in incorrect DNS
resolutions on MacOS. Check whether you have a dependency
on 'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to
see the full stack: java.lang.UnsatisfiedLinkError: failed to load the
required native library
build.gradle.kts 파일에 라이브러리 추가
runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.112.Fin
al:osx-aarch_64")
프로젝트 실행 시, 아래와 같은 에러가 나면 runtime
라이브러리를 추가합니다.
8
9. 실습 몸풀기 (2) - Spring AI 로 이미지 생성해보기
OpenAI 의 dall-e 3 모델에 프롬프트를 보내 이미지 생성하기
1. ImageController 추가
@Controller
@RequestMapping("/api/image")
class ImageController(
private val imageModel: ImageModel
) {
@GetMapping
fun image(@RequestParam query: String): String {
val response = imageModel.call(
ImagePrompt(
query,
ImageOptionsBuilder.builder()
.model("dall-e-3")
.build()
)
)
return "redirect:${response.result.output.url}"
}
}
2. 브라우저에서 결과 확인
- http://localhost:8080/api/image?query=귀여운고양이
9
10. 실습 몸풀기 (3) - Prompt Template 사용해보기
Prompt Template 적용하기
1. ChatController 에 Prompt Template 를 이용한 API 추가
@RestController
@RequestMapping("/api/chat")
class ChatController(
chatModel: OpenAiChatModel
) {
private val chatClient = ChatClient.builder(chatModel).build()
@GetMapping("/hello")
fun hello(@RequestParam name: String): String {
return chatClient.prompt()
.user{ userPrompt ->
userPrompt
.text("Hello, my name is {name}")
.param("name", name)
}
.call()
.content()!!
}
}
2. 브라우저에서 결과 확인
http://localhost:8080/api/chat/hello?name=황민호
ERROR 62236 --- [spring-ai-start] [ restartedMain]
i.n.r.d.DnsServerAddressStreamProviders : Unable to load
io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamPro
vider, fallback to system defaults. This may result in incorrect DNS
resolutions on MacOS. Check whether you have a dependency
on 'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to
see the full stack: java.lang.UnsatisfiedLinkError: failed to load the
required native library
build.gradle.kts 파일에 라이브러리 추가
runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.112.Fin
al:osx-aarch_64")
프로젝트 실행 시, 아래와 같은 에러가 나면 runtime
라이브러리를 추가합니다.
10
11. 실습 몸풀기 (3) - Prompt Template 사용해보기
Prompt Template 적용하기
2. hello-bot-template.st 파일 추가 3. hello-bot-template.st
@RestController
@RequestMapping("/api/chat")
class ChatController(
chatModel: OpenAiChatModel,
@Value("classpath:/prompts/hello-bot-template.st")
private val helloBotTemplate: Resource,
) {
private val chatClient = ChatClient.builder(chatModel).build()
@GetMapping("/hello")
fun hello(@RequestParam name: String): String {
return chatClient.prompt()
.user{ userPrompt ->
userPrompt
.text(helloBotTemplate)
.param("name", name)
}
.call()
.content()!!
}
}
Hello, my name is {name}
11
13. 생성형 AI 개념 이해하기 (4) - Token
LLM은 토큰 단위로 프롬프트를 이해하고 처리
1. 토큰으로 분리된 문장
2. GPT-4o 모델의 컨텍스트 사이즈 및 Output 토큰 최대 값
13
14. 생성형 AI 개념 이해하기 (4) - Token
LLM은 토큰 단위로 프롬프트를 이해하고 처리
3. 토큰 = 비용
(100만 토큰 당 비용) 배치 수행 시 비용
14
15. 생성형 AI 개념 이해하기 (4) - Token
LLM은 토큰 단위로 프롬프트를 이해하고 처리
4. 프롬프트 캐싱
- 캐시 히트는 프롬프트 내에서 앞쪽부터 일치할 때 가능
- 1,024개 이상의 토큰에서 128개 토큰 단위로 캐시가 동작
- 프롬프트 구성 시 동적인 부분을 뒤쪽으로 배치해야 효과적
5. 토큰 사용량 확인
"usage": {
"prompt_tokens": 2006,
"completion_tokens": 300,
"total_tokens": 2306,
"prompt_tokens_details": {
"cached_tokens": 1920
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
}
- Response 의 usage 에서 토큰 사용량 확인 가능
https://platform.openai.com/docs/guides/prompt-caching
15
16. 생성형 AI 개념 이해하기 (5) - Message
AI 모델에 Message 로 데이터를 주고 받습니다
1. Message Type
- OpenAI Chat API 에서 지원하는 Message Type
- Developer message //o1이후 system 대체
- System message
- User message
- Assistant message
- Tool message
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
}
- Spring AI 에서 지원하는 Message Type
16
17. 생성형 AI 개념 이해하기 (5) - Message
AI 모델에 Message 로 데이터를 주고 받습니다
2. Message History
- 챗봇에서는 매 요청마다 이전 대화 이력을 보내야 대화 맥락이 유지
@PostMapping
fun chat(@RequestParam query: String,
@RequestBody messages: List<Message>): String {
return chatClient.prompt()
.messages(
messages
// listOf(
// SystemMessage("당신은 날씨 도우미 챗봇입니다."),
// UserMessage("안녕?"),
// AssistantMessage("안녕하세요 . 무엇을 도와드릴까요 ?"),
// )
)
.user("오늘 판교 날씨 어때?")
.messages()
.call()
.content()!!
}
3. ChatOptions
- 디폴트 옵션을 지정하고, 런타임에도 옵션을 변경할 수 있음
17
18. 생성형 AI 개념 이해하기 (6) - 구조화 된 응답
AI 모델로부터 구조화 된 JSON 스키마 응답을 생성하도록 합니다
1. Structured Output
- OpenAI API 에서는 response_format 파라미터를 통해 구조화된 결과를 받을 수 있도록 제공합니다
- Converter로 AI가 생성하는 JSON 데이터를 Java 클래스와 같은 데이터 유형으로 빠르게 변환합니다
18
19. 심플한 여행도우미 챗봇 만들기 실습 (1) - 페르소나
여행 도우미 페르소나를 지정하고 간단한 대화 수행
1. ChatClient Bean 추가
- ChatClient 를 쉽게 사용하기 위해 Bean 으로 등록합니다.
@Configuration
class AIConfig {
@Bean
fun openAIChatClient(
chatModel: OpenAiChatModel): ChatClient {
return ChatClient.builder(chatModel)
.defaultOptions(
ChatOptions.builder()
.model("gpt-4o")
.temperature(0.0)
.build()
)
.build()
}
}
@Configuration
class AIConfig {
@Bean
fun openAIChatClient(
chatModel: OpenAiChatModel): ChatClient {
return ChatClient.builder(chatModel)
.build()
}
}
- 디폴트 모델 및 옵션 변경 예시
19
20. 심플한 여행도우미 챗봇 만들기 실습 (1) - 페르소나
여행 도우미 페르소나를 지정하고 간단한 대화 수행
2. TravelController 생성
- 시스템 프롬프트에 페르소나를 지정합니다.
@RestController
@RequestMapping("/api/travel")
class TravelController(
private val chatClient: ChatClient
) {
@GetMapping
fun travelChat(@RequestParam query: String): String {
return chatClient.prompt()
.system(
"당신은 여행 도우미 '춘식 여행봇' 입니다. " +
"여행에 유용한 정보를 제공합니다.")
.user(query)
.call()
.content()!!
}
}
3. 브라우저에서 결과 확인
http://localhost:8080/api/travel?query=너는 누구야?
안녕하세요! 저는 여행 도우미 '춘식 여행봇'입니다. 여행 계획을
세우거나 여행지에 대한 정보를 찾는 데 도움을 드릴 수 있어요.
궁금한 점이 있으면 언제든지 물어보세요!
http://localhost:8080/api/travel?query=판교 근처 갈만한 곳 알려줘
판교는 경기도 성남시에 위치한 현대적인 도시로, 다양한
볼거리와 즐길 거리가 있습니다. 판교 근처에서 방문할 만한 몇
가지 장소를 소개해드릴게요. 1. **판교 테크노밸리**: IT
기업들이 밀집해 있는 곳으로, 현대적인 건축물과 함께 다양한
스타트업 기업들을 볼 수 있습니다. 기술과 혁신에 관심이
있다면 방문해보세요. 2. **현대백화점 판교점**: 쇼핑을 즐기고
싶다면 현대백화점 판교점을 추천합니다. 다양한 브랜드와
맛집이 있어 쇼핑과 식사를 동시에 즐길 수 있습니다.
(이하 생략..)
20
21. 심플한 여행도우미 챗봇 만들기 실습 (1) - 페르소나
여행 도우미 페르소나를 지정하고 간단한 대화 수행
3. 추천 여행지를 리스트 객체로 받기
- AI의 결과를 리스트 객체로 받아 활용합니다
@GetMapping
fun travelChat(@RequestParam query: String):
List<PlaceResponse> {
return chatClient.prompt()
.system(
"당신은 여행 도우미 '춘식 여행봇' 입니다. " +
"여행에 유용한 정보를 제공합니다.")
.user(query)
.call()
.entity(object :
ParameterizedTypeReference<List<PlaceResponse>>() {})
?: emptyList()
}
}
data class PlaceResponse(
val name: String,
val address: String,
val description: String,
)
3. 브라우저에서 결과 확인
http://localhost:8080/api/travel?query=판교 근처 갈 만한 곳 알려줘
21
22. 심플한 여행도우미 챗봇 만들기 실습 (2) - 프롬프트 엔지니어링
보다 유용하고 친절한 여행도우미로 업그레이드
1. 여행도우미 프롬프트 고도화
- 의도에 맞는 답변을 하도록 기존 프롬프트를 보다 상세하게 작성
- AI를 이용하여 프롬프트 작성을 시작하는 것도 추천 <Anthropic Prompt Generator>
22
23. 심플한 여행도우미 챗봇 만들기 실습 (2) - 프롬프트 엔지니어링
보다 유용하고 친절한 여행도우미로 업그레이드
2. 프롬프트 적용
@RestController
@RequestMapping("/api/travel")
class TravelController(
private val chatClient: ChatClient,
@Value("classpath:/prompts/travel-bot-template.st")
private val travelBotTemplate: Resource,
) {
@GetMapping
fun travelChat(@RequestParam query: String): String {
return chatClient.prompt()
.system(travelBotTemplate)
.user(query)
.call()
.content()!!
}
}
23
24. 생성형 AI 개념 이해하기 (7) - 스트리밍 응답 처리
AI 모델로부터 스트리밍 응답을 받아 처리합니다
1. Streaming Model
- Spring AI 에서는 WebFlux 와 SSE (Server Sent Event)를 통해 스트리밍을 처리합니다
// 일반 응답 처리
val response: ChatResponse? = chatClient.prompt()
.user(query)
.call()
.chatResponse()
// 스트리밍 응답 처리
val response: Flux<ChatResponse> = chatClient.prompt()
.user(query)
.stream()
.chatResponse()
24
25. 심플한 여행도우미 챗봇 만들기 실습 (3) - 스트리밍 응답 처리로 변경
스트리밍 응답으로 변경 후 지연시간을 줄여 체감 속도를 높입니다
1. 프롬프트 적용
@RestController
@RequestMapping("/api/travel")
class TravelController(
private val chatClient: ChatClient,
@Value("classpath:/prompts/travel-bot-template.st")
private val travelBotTemplate: Resource,
) {
@GetMapping(produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun travelChat(@RequestParam query: String): Flux<ServerSentEvent<String>> {
return chatClient.prompt()
.system(travelBotTemplate)
.user(query)
.stream()
.content()
.map {
ServerSentEvent.builder<String>()
.event("text")
.data(it)
.build()
}
}
}
2. 브라우저에서 확인
25
26. 심플한 여행도우미 챗봇 만들기 실습 (3) - 스트리밍 응답 처리로 변경
스트리밍 응답으로 변경 후 지연시간을 줄여 체감 속도를 높입니다
3. 버퍼 및 타임아웃, 에러 처리
@GetMapping(produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun travelChat(@RequestParam query: String): Flux<ServerSentEvent<String>> {
return chatClient.prompt()
.system(travelBotTemplate)
.user(query)
.stream()
.content()
.bufferTimeout(100, Duration.ofMillis(100))
.map { it.joinToString("") }
.map {
ServerSentEvent.builder<String>()
.event("text").data(it).build()
}
.timeout(Duration.ofSeconds(120))
.onErrorResume { e ->
Flux.just(
ServerSentEvent.builder<String>()
.event("error")
.data("에러 발생: ${e.message}")
.build()
)
}
}
4. 브라우저에서 확인
26