티스토리 뷰

1. 지능형 애플리케이션 개발의 새로운 지평: Spring AI란 무엇인가?
우리는 지금 인공지능(AI) 기술, 특히 대규모 언어 모델(LLM, Large Language Model)이 전례 없는 속도로 발전하며 세상을 변화시키는 시대에 살고 있습니다. ChatGPT와 같은 LLM은 단순히 질문에 답하는 것을 넘어, 텍스트 생성, 요약, 번역, 코드 작성 등 다양한 작업을 수행하며 비즈니스와 일상생활에 혁신을 가져오고 있습니다. 이러한 강력한 AI 기능을 여러분의 애플리케이션에 통합하고 싶지만, 어디서부터 시작해야 할지 막막하셨나요? 특히 자바(Java) 개발 환경에서 이러한 최신 AI 기술을 활용하는 것이 어렵게 느껴졌을 수도 있습니다.
여기, 바로 그 고민을 해결해 줄 솔루션이 등장했습니다. 바로 스프링 AI(Spring AI) 입니다. 스프링 AI는 자바 개발자들이 친숙한 Spring 프레임워크를 이용해 손쉽게 LLM 기반 애플리케이션을 개발할 수 있도록 돕는 프로젝트입니다. 이 글은 기본적인 개발 지식이 있는 비전공자부터 Spring 프레임워크에 익숙한 전문가까지, 모든 분들이 Spring AI를 활용하여 인공지능 애플리케이션 개발의 첫걸음을 뗄 수 있도록 안내할 것입니다. Spring AI LLM 개발의 여정에 함께하시죠.
1.1. LLM과 스프링 프레임워크: Spring AI의 탄생 배경과 필요성
먼저, 대규모 언어 모델(LLM)에 대해 간략히 짚고 넘어가겠습니다. LLM은 방대한 양의 텍스트 데이터를 학습하여 인간처럼 자연스러운 언어를 이해하고 생성하는 능력을 가진 인공지능 모델입니다. OpenAI의 GPT-3/GPT-4, Google의 Bard/Gemini, Meta의 Llama 등이 대표적인 LLM이죠. 이 모델들은 API(Application Programming Interface) 형태로 제공되어, 개발자들이 자신의 서비스에 AI 기능을 쉽게 연동할 수 있도록 합니다.
하지만, 단순히 API를 호출하는 것만으로는 강력하고 안정적인 AI 애플리케이션을 만들기 어렵습니다. API 키 관리, 요청/응답 형식 처리, 에러 핸들링, 다양한 모델과의 호환성 등 고려해야 할 요소가 많습니다. 특히 자바 개발자에게는 기존 Spring 생태계와의 자연스러운 통합이 중요했습니다. Spring 프레임워크는 엔터프라이즈 환경에서 가장 널리 사용되는 자바 프레임워크 중 하나로, 방대한 기능과 안정성을 자랑합니다. 이러한 Spring 프레임워크의 강력함과 LLM의 지능을 결합하려는 시도에서 Spring AI가 탄생했습니다.
Spring AI는 Spring 생태계의 철학, 즉 "개발자 경험(Developer Experience) 최우선" 원칙을 LLM 통합에 그대로 적용합니다. 마치 데이터베이스를 연동하듯이, 메시징 시스템을 연동하듯이, LLM을 여러분의 Spring Boot 애플리케이션에 자연스럽게 통합할 수 있도록 설계되었습니다. 이는 자바 AI 개발의 진입 장벽을 크게 낮추는 동시에, 기존 Spring 개발자들이 익숙한 방식으로 새로운 기술을 활용할 수 있게 합니다.
1.2. Spring AI의 핵심 기능 및 특징: 왜 필요한가?
Spring AI는 단순히 LLM API를 호출하는 것 이상의 가치를 제공합니다. 다음은 Spring AI가 제공하는 주요 기능과 특징입니다.
- 추상화된 LLM 인터페이스: Spring AI는 다양한 LLM 제공업체(OpenAI, Hugging Face, Azure OpenAI, Google Gemini 등)를 하나의 통일된 인터페이스로 추상화합니다. 마치 JDBC(Java Database Connectivity)가 다양한 데이터베이스를 동일한 방식으로 다룰 수 있게 하는 것처럼, Spring AI는 어떤 LLM을 사용하든 코드 변경을 최소화하여 쉽게 전환할 수 있도록 돕습니다. 이는
Spring AI 사용법의 핵심적인 장점입니다. 여러분은 특정 LLM 기술에 종속되지 않고, 비즈니스 요구사항이나 성능, 비용에 따라 유연하게 모델을 변경할 수 있습니다. - 손쉬운 설정(Easy Configuration): Spring Boot의 자동 설정(Auto-configuration) 기능을 활용하여, 몇 줄의 설정만으로 LLM 서비스를 애플리케이션에 연동할 수 있습니다. API 키와 같은 민감한 정보는 환경 변수나 설정 파일(
.properties,.yml)을 통해 안전하게 관리할 수 있습니다. - 프롬프트 템플릿(Prompt Templates): LLM에게 어떤 작업을 지시할지 정의하는 것이 '프롬프트(Prompt)'입니다. 좋은 프롬프트를 작성하는 것은 LLM의 성능을 극대화하는 데 매우 중요합니다. Spring AI는 동적인 데이터를 주입할 수 있는 프롬프트 템플릿 기능을 제공하여, 재사용 가능하고 효율적인 프롬프트 관리를 가능하게 합니다. 이는 나중에
Spring AI 프롬프트 엔지니어링섹션에서 자세히 다룰 것입니다. - 출력 파서(Output Parsers): LLM의 응답은 주로 자유 형식의 텍스트입니다. 이 텍스트에서 필요한 정보를 추출하여 자바 객체나 특정 형식으로 변환하는 작업은 매우 중요합니다. Spring AI는 이러한 출력을 구조화된 데이터로 쉽게 파싱(Parsing)할 수 있도록 돕는 강력한 출력 파서 기능을 제공합니다. 이를 통해 LLM의 비정형 응답을 애플리케이션 내부에서 활용하기 쉬운 형태로 변환할 수 있습니다.
- 스트리밍 지원(Streaming Support): LLM은 긴 응답을 생성할 때 한 번에 모든 결과를 반환하기보다, 마치 사람이 타이핑하듯이 단어 단위로 스트리밍하여 응답을 보내는 경우가 많습니다. Spring AI는 이러한 스트리밍 응답을 비동기적으로 처리할 수 있도록 지원하여, 사용자 경험을 향상시키고 대기 시간을 줄여줍니다.
- 임베딩(Embedding) 및 벡터 데이터베이스 통합: LLM은 텍스트를 숫자의 벡터(vector) 형태로 변환하는 '임베딩' 기능을 가지고 있습니다. 이 임베딩은 텍스트 간의 의미론적 유사성을 계산하는 데 사용되며, 검색 증강 생성(RAG, Retrieval Augmented Generation)과 같은 고급 AI 애플리케이션 개발의 핵심입니다. Spring AI는 다양한 임베딩 모델을 지원하고, Qdrant, Chroma, PGVector 등 벡터 데이터베이스와의 연동을 통해 더욱 강력한 AI 기능을 구현할 수 있도록 돕습니다.
Spring AI는 이처럼 복잡하고 다양한 LLM 관련 기술들을 Spring 개발자에게 익숙한 방식으로 추상화하고 통합함으로써, 개발자들이 AI의 본질적인 문제 해결에 집중할 수 있도록 해줍니다. 이제 더 이상 Spring AI 예제를 찾아 헤맬 필요 없이, 이 글과 함께 여러분의 아이디어를 현실로 만들어갈 준비를 할 시간입니다. Spring AI를 통해 여러분의 애플리케이션에 지능을 불어넣는 첫걸음을 내딛어 봅시다.
2. Spring AI 개발 환경 설정 및 "Hello World" 예제: 첫 LLM 애플리케이션 만들기
이제 이론적인 설명을 넘어, 직접 Spring AI를 활용한 첫 번째 LLM 애플리케이션을 만들어보는 시간을 갖겠습니다. 이 섹션에서는 Spring AI 프로젝트를 시작하기 위한 개발 환경 설정부터 가장 기본적인 LLM 연동 "Hello World" 예제까지, 단계별로 자세히 안내합니다. Spring AI 사용법의 기초를 다지는 중요한 과정이니, 천천히 따라와 주세요.
2.1. 개발 환경 준비: 필수 도구 설치
Spring AI 프로젝트를 시작하기 전에, 몇 가지 필수 개발 도구가 필요합니다.
- 자바 개발 키트 (JDK): Spring AI는 Java 17 이상을 권장합니다.
- Oracle JDK 또는 OpenJDK 중 하나를 설치합니다.
- 빌드 도구 (Maven 또는 Gradle): 프로젝트 빌드 및 의존성 관리를 위해 필요합니다. Spring Initializr를 사용하면 자동으로 설정됩니다.
- 통합 개발 환경 (IDE): IntelliJ IDEA, Eclipse, VS Code 등 익숙한 IDE를 사용하시면 됩니다. 저는 IntelliJ IDEA를 기준으로 설명하겠습니다.
- OpenAI API 키: 이 예제에서는 OpenAI의 GPT 모델을 사용합니다. OpenAI 웹사이트에서 가입 후 API 키를 발급받아야 합니다.
- OpenAI 플랫폼에 접속하여 계정을 생성하고,
API keys메뉴에서 새로운 시크릿 키를 생성하세요. 이 키는 외부에 노출되지 않도록 주의해야 합니다. 다른 LLM 제공업체(예: Hugging Face, Google Gemini)를 사용하고 싶다면 해당 플랫폼에서 API 키를 발급받으면 됩니다.
- OpenAI 플랫폼에 접속하여 계정을 생성하고,
2.2. Spring Initializr를 이용한 프로젝트 생성
가장 쉽고 빠른 방법으로 Spring Boot 프로젝트를 생성하는 것은 Spring Initializr를 이용하는 것입니다.
- Spring Initializr 웹사이트 접속: 웹 브라우저에서
https://start.spring.io/에 접속합니다. - 프로젝트 설정:
- Project:
Maven Project또는Gradle Project(여기서는 Maven을 기준으로 진행합니다.) - Language:
Java - Spring Boot:
3.2.x또는 그 이상 버전 (Spring AI는 Spring Boot 3.2.0부터 공식 지원합니다.) - Group:
com.example(본인의 도메인에 맞게 변경 가능) - Artifact:
spring-ai-hello-world(프로젝트 이름) - Name:
spring-ai-hello-world - Description:
Demo project for Spring AI Hello World - Package Name:
com.example.springaihelloworld - Packaging:
Jar - Java:
17또는21
- Project:
- 의존성(Dependencies) 추가:
Add Dependencies...버튼을 클릭하고 다음 의존성을 검색하여 추가합니다.Spring Web(웹 애플리케이션 개발을 위한 기본 의존성)Spring AI OpenAI Starter(OpenAI 연동을 위한 Spring AI 스타터)- (선택 사항)
Lombok(코드 간결화를 위해)
- 프로젝트 생성 및 다운로드:
Generate버튼을 클릭하여 프로젝트 압축 파일을 다운로드합니다. - IDE로 프로젝트 임포트: 다운로드한
spring-ai-hello-world.zip파일의 압축을 해제하고, IntelliJ IDEA와 같은 IDE에서File > Open또는File > Import Project를 통해 해당 디렉토리를 엽니다. Maven 프로젝트이므로 IDE가 자동으로 의존성을 다운로드하고 프로젝트를 설정할 것입니다.
2.3. Spring AI 설정: OpenAI API 키 연동
프로젝트가 성공적으로 임포트되었다면, 이제 OpenAI API 키를 Spring AI에 설정해야 합니다. src/main/resources/application.properties 또는 application.yml 파일에 다음 설정을 추가합니다. 저는 application.properties를 사용하겠습니다.
# application.properties
spring.application.name=spring-ai-hello-world
# OpenAI API 키 설정
spring.ai.openai.api-key=${OPENAI_API_KEY}
# OpenAI 모델 설정 (gpt-4o, gpt-3.5-turbo 등 원하는 모델 선택)
spring.ai.openai.chat.options.model=gpt-4o
중요: ${OPENAI_API_KEY} 부분에는 실제 발급받은 OpenAI API 키를 직접 입력하는 대신, 환경 변수를 사용하는 것을 강력히 권장합니다. 이는 보안상 매우 중요한 관행입니다. 터미널에서 애플리케이션을 실행하기 전에 다음과 같이 환경 변수를 설정할 수 있습니다.
# Linux/macOS
export OPENAI_API_KEY="YOUR_ACTUAL_OPENAI_API_KEY"
java -jar build/libs/spring-ai-hello-world-0.0.1-SNAPSHOT.jar
# Windows (Command Prompt)
set OPENAI_API_KEY="YOUR_ACTUAL_OPENAI_API_KEY"
java -jar build/libs/spring-ai-hello-world-0.0.1-SNAPSHOT.jar
IDE에서 실행하는 경우, Run/Debug Configurations에서 환경 변수를 설정할 수 있습니다. 예를 들어, IntelliJ IDEA에서는 Edit Configurations... -> Environment variables 필드에 OPENAI_API_KEY=YOUR_ACTUAL_OPENAI_API_KEY와 같이 추가할 수 있습니다.
2.4. "Hello World" 예제: 첫 LLM 채팅 구현
이제 모든 준비가 끝났습니다. Spring AI 예제의 가장 기본적인 형태인 "Hello World"를 구현해봅시다. SpringAiHelloWorldApplication.java 파일에 다음과 같이 간단한 REST 컨트롤러를 추가하여, 웹 요청을 통해 LLM과 대화하는 기능을 만들어 보겠습니다.
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController // 이 클래스가 REST API를 제공하는 컨트롤러임을 나타냅니다.
public class ChatController {
private final ChatClient chatClient; // Spring AI의 ChatClient 인터페이스를 주입받습니다.
// 생성자 주입을 통해 ChatClient 인스턴스를 받습니다.
public ChatController(ChatClient chatClient) {
// Spring AI는 ChatClient 구현체를 자동으로 구성하여 빈으로 등록합니다.
// 따라서 직접 ChatClient.Builder를 사용하여 빌드할 필요 없이 바로 주입받을 수 있습니다.
this.chatClient = chatClient;
}
/**
* '/chat' 경로로 GET 요청이 오면 LLM과 대화하고 응답을 반환합니다.
* 예: http://localhost:8080/chat?message=오늘%20날씨는?
* @param message 사용자로부터 받은 질문 메시지
* @return LLM의 응답 텍스트
*/
@GetMapping("/chat")
public String chat(@RequestParam(value = "message", defaultValue = "Spring AI에 대해 알려줘") String message) {
// ChatClient를 사용하여 LLM에 메시지를 보내고 응답을 받습니다.
// .prompt() 메서드로 사용자 프롬프트를 설정하고,
// .call() 메서드로 LLM을 호출한 후,
// .content() 메서드로 LLM 응답의 텍스트 내용을 추출합니다.
return chatClient.prompt()
.user(message) // 사용자 메시지를 프롬프트로 전달합니다.
.call() // LLM을 호출합니다.
.content(); // LLM의 응답 내용(텍스트)을 반환합니다.
}
/**
* '/joke' 경로로 GET 요청이 오면 LLM에게 농담을 요청하고 응답을 반환합니다.
* 이 예제는 단순히 프롬프트에 고정된 내용을 전달하는 방식입니다.
* 예: http://localhost:8080/joke
* @return LLM이 생성한 농담
*/
@GetMapping("/joke")
public String generateJoke() {
return chatClient.prompt()
.user("재미있는 농담 하나 해줘.") // 고정된 농담 요청 프롬프트
.call()
.content();
}
}
코드 설명:
@RestController: 이 애노테이션은 이 클래스가 REST API 요청을 처리하는 컨트롤러임을 Spring에게 알립니다.ChatClient주입: Spring AI의 핵심 인터페이스 중 하나인ChatClient를 주입받습니다.ChatClient는 LLM과의 상호작용을 담당하는 역할을 합니다. Spring AI는 자동으로 이 인터페이스의 구현체를 생성하여 주입해줍니다.chat메서드:@GetMapping("/chat"):/chat경로로 들어오는 GET 요청을 이 메서드가 처리하도록 매핑합니다.@RequestParam(value = "message", defaultValue = "Spring AI에 대해 알려줘") String message: URL 파라미터message값을 받아옵니다. 만약message파라미터가 없으면 기본값으로 "Spring AI에 대해 알려줘"가 사용됩니다.chatClient.prompt().user(message).call().content(): 이 부분이 실제로 LLM과 상호작용하는 핵심 코드입니다.chatClient.prompt(): LLM에게 보낼 프롬프트(질문)를 구성하기 시작합니다..user(message): 사용자 역할을 하는 메시지를 추가합니다. LLM은 이 메시지를 바탕으로 응답을 생성합니다..call(): 구성된 프롬프트를 사용하여 LLM을 호출합니다. 실제 OpenAI API 호출이 이 시점에서 발생합니다..content(): LLM으로부터 받은 응답 객체(ChatResponse)에서 실제 텍스트 내용만을 추출하여 반환합니다.
generateJoke메서드:/joke경로로 요청이 오면 LLM에게 고정된 농담 요청 프롬프트를 보내는 간단한 예제입니다.
2.5. 애플리케이션 실행 및 테스트
- 애플리케이션 실행: IDE에서
SpringAiHelloWorldApplication.java파일을 실행합니다. 또는 Maven을 사용하는 경우 프로젝트 루트 디렉토리에서 다음 명령어를 실행합니다.애플리케이션이 성공적으로 시작되면 콘솔에Started SpringAiHelloWorldApplication in ...와 같은 메시지가 출력됩니다. # 환경 변수 설정 후 실행 export OPENAI_API_KEY="YOUR_ACTUAL_OPENAI_API_KEY" # macOS/Linux # set OPENAI_API_KEY="YOUR_ACTUAL_OPENAI_API_KEY" # Windows ./mvnw spring-boot:run- 웹 브라우저 또는 Postman으로 테스트:
- 웹 브라우저를 열고 다음 URL로 접속합니다.
http://localhost:8080/chat?message=스프링%20AI는%20무엇인가요?http://localhost:8080/chat(기본 메시지 사용)http://localhost:8080/joke
- 잠시 후, LLM으로부터 받은 응답 텍스트가 브라우저 화면에 표시되는 것을 확인할 수 있습니다.
- 웹 브라우저를 열고 다음 URL로 접속합니다.
이것으로 여러분은 Spring AI를 이용한 첫 LLM 애플리케이션을 성공적으로 개발하고 실행했습니다! Spring AI OpenAI 연동이 얼마나 간단한지 경험하셨기를 바랍니다. 다음 섹션에서는 더 다양한 LLM을 연동하는 방법에 대해 알아보겠습니다.
3. 다양한 LLM 연동하기: OpenAI, Hugging Face 모델 활용 예제
앞서 "Hello World" 예제에서는 OpenAI의 GPT 모델을 사용했습니다. 하지만 Spring AI의 가장 큰 장점 중 하나는 바로 다양한 대규모 언어 모델(LLM) 제공업체를 추상화하여, Spring AI LLM 개발을 더욱 유연하게 만든다는 점입니다. 이 섹션에서는 Spring AI를 사용하여 OpenAI 외에 Hugging Face와 같은 다른 LLM을 통합하고 활용하는 구체적인 예제를 제공합니다. 여러분의 특정 요구사항이나 환경에 맞춰 최적의 LLM을 선택하고 쉽게 전환하는 방법을 익히게 될 것입니다.
3.1. Spring AI의 공급자 추상화 이해
Spring AI는 ChatClient라는 핵심 인터페이스를 통해 모든 LLM 공급자를 동일한 방식으로 다룰 수 있도록 합니다. 이는 마치 자바 개발에서 java.sql.Connection 인터페이스가 MySQL, PostgreSQL, Oracle 등 어떤 데이터베이스든 동일한 방식으로 접근할 수 있도록 하는 것과 같습니다. 개발자는 특정 LLM API의 복잡한 구현 세부 사항에 얽매이지 않고, 오직 ChatClient 인터페이스만을 통해 대화 기능을 구현하면 됩니다.
LLM 공급자를 변경하는 것은 주로 설정 파일(application.properties 또는 application.yml)의 몇 줄을 수정하는 것만으로 가능하며, 대부분의 경우 애플리케이션 코드는 거의 변경할 필요가 없습니다. 이것이 바로 Spring AI 사용법의 강력함이자 효율성입니다.
3.2. OpenAI 모델 다시 보기: 고급 설정
이전 섹션에서 OpenAI의 GPT 모델을 이미 사용해봤지만, 몇 가지 고급 설정을 통해 LLM의 동작을 더욱 세밀하게 제어할 수 있습니다.
application.properties 파일에서 OpenAI 설정을 조정할 수 있습니다:
# application.properties
# OpenAI API 키 (환경 변수 사용 권장)
spring.ai.openai.api-key=${OPENAI_API_KEY}
# OpenAI Chat 모델 관련 설정
spring.ai.openai.chat.options.model=gpt-4o # 사용할 모델 (예: gpt-3.5-turbo, gpt-4o)
spring.ai.openai.chat.options.temperature=0.7 # 모델의 창의성/무작위성 (0.0은 가장 일관적, 1.0은 가장 창의적)
spring.ai.openai.chat.options.top-p=1.0 # 샘플링 방식 제어 (높을수록 다양한 단어 선택)
spring.ai.openai.chat.options.max-tokens=500 # 생성될 응답의 최대 토큰 수
spring.ai.openai.chat.options.presence-penalty=0.0 # 새로운 주제 생성 장려/억제 (-2.0 ~ 2.0)
spring.ai.openai.chat.options.frequency-penalty=0.0 # 반복되는 단어/구절 사용 장려/억제 (-2.0 ~ 2.0)
설정 옵션 설명:
model: 사용할 LLM의 이름을 지정합니다.gpt-4o는 OpenAI의 최신 모델 중 하나이며,gpt-3.5-turbo는 비용 효율적인 대안입니다.temperature: 응답의 "창의성" 또는 "무작위성"을 조절합니다. 낮은 값(예: 0.2)은 더 예측 가능하고 일관된 응답을 생성하고, 높은 값(예: 0.8)은 더 다양하고 창의적인 응답을 생성합니다.top-p:temperature와 함께 샘플링 방식을 제어하는 매개변수입니다. 예를 들어top-p=0.9는 확률이 높은 상위 90%의 토큰 중에서만 단어를 선택하도록 합니다.max-tokens: LLM이 생성할 수 있는 응답의 최대 토큰(단어 또는 구의 일부) 수를 제한합니다.presence-penalty,frequency-penalty: LLM이 새로운 주제를 얼마나 자주 도입할지(presence) 또는 특정 단어를 얼마나 자주 반복할지(frequency) 제어하여 응답의 다양성을 조절합니다.
이러한 옵션들을 조정함으로써 Spring AI OpenAI 연동 시 모델의 동작을 여러분의 애플리케이션 요구사항에 맞게 미세 조정할 수 있습니다.
3.3. Hugging Face 모델 연동하기
Hugging Face는 대규모 언어 모델을 포함한 다양한 머신러닝 모델을 호스팅하고 공유하는 커뮤니티 플랫폼입니다. Spring AI는 Hugging Face Hub에 호스팅된 모델을 쉽게 연동할 수 있도록 지원합니다. Hugging Face Inference API를 사용하거나, 로컬에서 모델을 실행할 수도 있습니다. 여기서는 Hugging Face Inference API를 사용하는 방법을 살펴보겠습니다.
3.3.1. Hugging Face API 키 발급
- Hugging Face 웹사이트에 접속하여 계정을 생성하거나 로그인합니다.
Settings->Access Tokens메뉴로 이동하여 새로운 토큰을 생성합니다.Read권한으로 충분합니다. 이 토큰은HF_API_TOKEN환경 변수로 사용될 것입니다.
3.3.2. 프로젝트 의존성 추가
pom.xml 파일에 spring-ai-huggingface-starter 의존성을 추가합니다. 기존 spring-ai-openai-starter는 주석 처리하거나 제거하여 혼동을 피할 수 있습니다.
<!-- pom.xml -->
<dependencies>
<!-- ... 기존 의존성 ... -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-huggingface-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version> <!-- ${spring-ai.version}은 properties에 정의되어 있어야 합니다. -->
</dependency>
<!-- OpenAI Starter는 주석 처리하거나 제거 -->
<!--
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
-->
</dependencies>
pom.xml의 <properties> 섹션에 spring-ai.version이 정의되어 있는지 확인하세요. 만약 없다면, Spring Initializr에서 생성할 때 자동으로 추가되었을 것입니다.
3.3.3. Hugging Face 설정
application.properties 또는 application.yml 파일에서 Hugging Face 설정을 추가합니다. OpenAI 설정을 주석 처리하거나 제거하고 Hugging Face 설정을 활성화합니다.
# application.properties
# Hugging Face API 키 (환경 변수 사용 권장)
spring.ai.huggingface.api-key=${HF_API_TOKEN}
# 사용할 Hugging Face 모델 ID (예: "microsoft/phi-2", "google/flan-t5-base")
# https://huggingface.co/models 에서 원하는 텍스트 생성 모델을 찾아 ID를 입력하세요.
# chat 모델이 아닌 text-generation 모델을 선택하는 경우가 많습니다.
spring.ai.huggingface.chat.options.model=google/flan-t5-base
모델 선택 주의사항:
Hugging Face Hub에는 수많은 모델이 있습니다. chat.options.model에 지정할 모델은 텍스트 생성(Text Generation) 또는 채팅(Chat) 기능을 제공하는 모델이어야 합니다. google/flan-t5-base와 같은 모델은 텍스트 생성에 적합하며, microsoft/phi-2와 같은 모델도 활용될 수 있습니다. 특정 모델의 API 엔드포인트가 없을 경우 에러가 발생할 수 있으니, Hugging Face 모델 페이지에서 'Deploy' 탭을 확인하여 Inference API를 지원하는지 확인하는 것이 좋습니다.
3.3.4. Hugging Face 연동 예제 코드
ChatController 코드는 ChatClient 인터페이스를 사용하므로, 별도로 수정할 필요가 없습니다. 이것이 바로 Spring AI 추상화의 강력함입니다!
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/chat")
public String chat(@RequestParam(value = "message", defaultValue = "스프링 AI에 대해 알려줘") String message) {
// 이 코드는 Hugging Face 모델이 연결되어 있더라도 동일하게 작동합니다!
return chatClient.prompt()
.user(message)
.call()
.content();
}
@GetMapping("/joke")
public String generateJoke() {
return chatClient.prompt()
.user("재미있는 농담 하나 해줘.")
.call()
.content();
}
}
이제 애플리케이션을 다시 실행하고 (HF_API_TOKEN 환경 변수를 설정했는지 확인하세요!), http://localhost:8080/chat?message=자바란%20무엇인가요? 와 같은 URL로 접속하면, 이번에는 Hugging Face 모델이 응답을 생성하는 것을 볼 수 있습니다.
3.4. 다른 LLM 제공업체 연동 (간략 소개)
Spring AI는 OpenAI, Hugging Face 외에도 다음과 같은 다양한 LLM 제공업체를 지원합니다.
- Azure OpenAI Service: 마이크로소프트 Azure 클라우드에서 OpenAI 모델을 배포하여 사용합니다. 기업 환경에서 보안 및 거버넌스 요구사항을 충족하며 OpenAI 모델을 활용할 때 유용합니다. (의존성:
spring-ai-azure-openai-spring-boot-starter) - Google Gemini: 구글의 최신 멀티모달 LLM인 Gemini를 연동합니다. (의존성:
spring-ai-google-gemini-spring-boot-starter) - Ollama: 로컬 머신에서 오픈소스 LLM을 실행할 수 있도록 돕는 도구입니다. 인터넷 연결 없이도 LLM을 테스트하거나 민감한 데이터를 처리할 때 유용합니다. (의존성:
spring-ai-ollama-spring-boot-starter) - AWS Bedrock: 아마존 웹 서비스(AWS)의 완전 관리형 LLM 서비스입니다. (의존성:
spring-ai-aws-bedrock-spring-boot-starter)
이러한 모든 제공업체는 동일한 ChatClient 인터페이스를 사용하므로, 기본 설정만 변경하면 쉽게 모델을 전환하고 테스트할 수 있습니다. 이는 Spring AI LLM 개발의 유연성을 극대화하며, 특정 LLM에 대한 종속성을 줄여줍니다. 여러분은 이제 필요한 LLM을 자유롭게 선택하고 Spring AI 예제를 확장해 나갈 수 있는 기반을 마련했습니다.
4. 고급 기능 활용: 프롬프트 엔지니어링 및 출력 파서 예제
지금까지 Spring AI를 사용하여 LLM과 기본적인 대화를 나누는 방법을 배웠습니다. 하지만 LLM의 잠재력을 최대한 끌어내고, 애플리케이션이 요구하는 정확하고 구조화된 응답을 받기 위해서는 단순한 질문 이상의 기술이 필요합니다. 이 섹션에서는 프롬프트 엔지니어링(Prompt Engineering) 기법과 LLM의 출력을 원하는 형식으로 가공하는 출력 파서(Output Parser) 에 대한 심화 예제를 다룹니다. 이 두 가지 고급 기능을 마스터하는 것은 Spring AI 프롬프트 엔지니어링의 핵심이며, 실용적인 Spring AI LLM 개발의 필수 요소입니다.
4.1. 프롬프트 엔지니어링: LLM과의 대화 기술
프롬프트 엔지니어링은 LLM에게 우리가 원하는 작업을 정확하게 수행하도록 지시하는 '질문 또는 지시문 작성 기술'입니다. "좋은 프롬프트"는 모호하지 않고, 구체적이며, 필요한 맥락을 제공하고, 원하는 출력 형식을 명시하는 특징을 가집니다. 마치 사람에게 작업을 지시할 때 구체적인 가이드라인을 주는 것과 같습니다.
Spring AI는 PromptTemplate과 ChatPromptTemplate을 통해 효율적인 프롬프트 관리를 지원합니다.
4.1.1. PromptTemplate을 활용한 동적 프롬프트 생성
PromptTemplate은 변수를 포함하는 템플릿 문자열을 정의하고, 런타임에 이 변수들을 실제 값으로 채워 동적인 프롬프트를 생성할 수 있게 합니다. 이는 동일한 패턴의 질문을 반복하거나, 사용자 입력에 따라 질문의 내용을 변경해야 할 때 매우 유용합니다.
예제: 동적인 요약 요청 프롬프트
사용자가 제공한 문서의 특정 주제에 대한 요약을 요청하는 시나리오를 생각해봅시다.
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class PromptEngineeringController {
private final ChatClient chatClient;
public PromptEngineeringController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 동적인 PromptTemplate을 사용하여 문서의 특정 주제를 요약합니다.
* 예: http://localhost:8080/summarize?topic=스프링%20AI&document=스프링%20AI는%20자바%20개발자들이...
* @param topic 요약할 주제
* @param document 요약 대상 문서 내용
* @return LLM이 요약한 텍스트
*/
@GetMapping("/summarize")
public String summarizeDocument(
@RequestParam(value = "topic", defaultValue = "Spring AI") String topic,
@RequestParam(value = "document", defaultValue = "Spring AI는 대규모 언어 모델(LLM)을 Spring 프레임워크 기반 애플리케이션에 통합하는 것을 돕는 오픈소스 프로젝트입니다. 자바 개발자들이 친숙한 방식으로 AI 기능을 활용할 수 있게 합니다. 주요 기능으로는 LLM 공급자 추상화, 프롬프트 템플릿, 출력 파서 등이 있습니다.") String document) {
// 1. 프롬프트 템플릿 정의: 변수 {topic}과 {document}를 포함합니다.
String template = """
다음 문서를 읽고 '{topic}' 주제에 초점을 맞춰 핵심 내용을 3줄 이내로 요약해줘.
문서:
{document}
""";
// 2. PromptTemplate 객체 생성
PromptTemplate promptTemplate = new PromptTemplate(template);
// 3. 변수에 실제 값을 매핑하여 프롬프트 메시지 생성
// Map.of()를 사용하여 템플릿 변수에 값을 할당합니다.
String formattedPrompt = promptTemplate.render(Map.of("topic", topic, "document", document));
// 4. LLM 호출 및 응답 반환
return chatClient.prompt()
.user(formattedPrompt) // 템플릿으로 생성된 동적인 프롬프트를 전달합니다.
.call()
.content();
}
}
코드 설명:
PromptTemplate에 정의된{topic}과{document}플레이스홀더가Map.of()를 통해 전달된 실제 값으로 대체됩니다.- 이렇게 생성된 동적인 프롬프트가
chatClient.prompt().user(formattedPrompt)를 통해 LLM에 전달됩니다. - 이 방식은 동일한 요약 로직을 여러 상황에서 재사용할 때 코드 중복을 줄이고 유지보수성을 높여줍니다.
4.1.2. ChatPromptTemplate을 활용한 역할 기반 대화
실제 챗봇 애플리케이션에서는 사용자(User) 메시지뿐만 아니라 시스템(System) 메시지나 AI(Assistant) 메시지 등 여러 역할의 메시지가 오고 갑니다. ChatPromptTemplate은 이러한 다중 역할 대화를 효과적으로 관리하고, LLM에게 특정 역할을 부여하거나 대화의 맥락을 제공하는 데 사용됩니다.
예제: 시스템 메시지를 포함한 챗봇 대화
시스템 메시지를 통해 LLM에게 "친절하고 유용한 AI 비서" 역할을 부여하고, 사용자의 질문에 답변하도록 해봅시다.
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatPromptTemplate;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.prompt.messages.Message;
import org.springframework.ai.chat.prompt.messages.UserMessage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class RoleBasedChatController {
private final ChatClient chatClient;
public RoleBasedChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 시스템 메시지를 사용하여 LLM의 역할을 정의하고 대화합니다.
* 예: http://localhost:8080/rolechat?query=나는%20오늘%20무엇을%20해야%20할까?
* @param query 사용자 질문
* @return LLM의 응답
*/
@GetMapping("/rolechat")
public String roleBasedChat(@RequestParam(value = "query", defaultValue = "Spring AI에 대해 알려줘") String query) {
// 1. 시스템 프롬프트 정의: LLM의 역할을 설정합니다.
// LLM에게 "너는 친절하고 유용한 AI 비서야. 질문에 짧고 명확하게 답변해줘."라고 지시합니다.
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("""
너는 친절하고 유용한 AI 비서야. 질문에 짧고 명확하게 답변해줘.
""");
// 2. 사용자 메시지 정의: 사용자의 실제 질문입니다.
UserMessage userMessage = new UserMessage(query);
// 3. ChatPromptTemplate을 사용하여 전체 프롬프트 구성
// 시스템 메시지와 사용자 메시지를 순서대로 리스트에 담습니다.
// 이렇게 하면 LLM은 시스템 메시지의 지침을 먼저 이해하고 사용자 메시지에 응답합니다.
List<Message> messages = List.of(
systemPromptTemplate.createMessage(), // 시스템 메시지를 Message 객체로 변환
userMessage // 사용자 메시지
);
ChatPromptTemplate chatPromptTemplate = ChatPromptTemplate.fromMessages(messages);
// 4. LLM 호출 및 응답 반환
return chatClient.prompt()
.from(chatPromptTemplate) // 구성된 ChatPromptTemplate을 사용합니다.
.call()
.content();
}
}
코드 설명:
SystemPromptTemplate을 사용하여 LLM에게 특정 역할을 부여하는 "시스템 메시지"를 정의합니다.UserMessage는 사용자의 실제 질문을 나타냅니다.ChatPromptTemplate.fromMessages()를 통해 시스템 메시지와 사용자 메시지를 순서대로 포함하는 전체 대화 맥락을 구성합니다.chatClient.prompt().from(chatPromptTemplate)를 사용하여 이 대화 맥락을 LLM에 전달합니다.
이렇게 역할 기반 대화를 사용하면, LLM이 일관된 페르소나를 유지하거나 특정 지침(예: "답변을 3줄 이내로 제한해줘", "전문적인 어조로 답변해줘")을 따르도록 유도할 수 있어, Spring AI 프롬프트 엔지니어링의 중요한 기술이 됩니다.
4.2. 출력 파서(Output Parser): LLM 응답을 구조화된 데이터로 변환
LLM은 주로 자유 형식의 텍스트를 반환합니다. 하지만 애플리케이션에서는 이 텍스트에서 특정 정보를 추출하여 자바 객체로 변환하거나, JSON, XML과 같은 구조화된 형태로 받아야 할 필요가 많습니다. Spring AI의 출력 파서(Output Parser)는 이러한 작업을 자동화하고 간소화합니다.
4.2.1. BeanOutputParser를 이용한 JSON 응답 파싱
LLM에게 특정 형식(예: JSON)으로 응답을 요청하고, 그 응답을 자바 객체(Java Bean)로 자동으로 매핑하는 것이 BeanOutputParser의 주요 기능입니다.
예제: 도시 정보 JSON 응답 파싱
LLM에게 특정 도시의 이름과 날씨를 JSON 형식으로 요청하고, 이를 CityInfo 자바 객체로 파싱해봅시다.
먼저, 응답을 저장할 자바 POJO(Plain Old Java Object) 클래스를 정의합니다.
// src/main/java/com/example/springaihelloworld/CityInfo.java
package com.example.springaihelloworld;
import lombok.Data; // Lombok을 사용하면 Getter/Setter/ToString 등을 자동으로 생성합니다.
@Data // @Data 애노테이션은 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 포함합니다.
public class CityInfo {
private String cityName;
private String weather;
private String temperature;
}
이제 이 CityInfo 객체로 파싱하도록 컨트롤러 코드를 작성합니다.
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class OutputParserController {
private final ChatClient chatClient;
public OutputParserController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* BeanOutputParser를 사용하여 LLM의 JSON 응답을 자바 객체로 파싱합니다.
* 예: http://localhost:8080/cityinfo?city=서울
* @param city 정보를 얻고 싶은 도시 이름
* @return 파싱된 CityInfo 객체 (JSON 형식으로 자동 직렬화되어 반환)
*/
@GetMapping("/cityinfo")
public CityInfo getCityInfo(@RequestParam(value = "city", defaultValue = "서울") String city) {
// 1. BeanOutputParser 생성: CityInfo 클래스로 파싱하도록 지정합니다.
BeanOutputParser<CityInfo> parser = new BeanOutputParser<>(CityInfo.class);
// 2. 프롬프트 템플릿 정의: LLM에게 JSON 형식의 응답을 요청합니다.
// parser.getFormat() 메서드는 BeanOutputParser가 기대하는 JSON 형식을 LLM에게 알려주는 지시문을 생성합니다.
String promptString = """
'{city}' 도시의 현재 날씨와 온도를 알려줘. 응답은 다음과 같은 JSON 형식으로 해줘:
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString, Map.of("city", city, "format", parser.getFormat()));
// 3. LLM 호출 및 응답 파싱
return chatClient.prompt()
.user(promptTemplate.render()) // 템플릿으로 렌더링된 프롬프트 전달
.call()
.content()
.parse(parser); // BeanOutputParser를 사용하여 LLM의 응답을 CityInfo 객체로 파싱합니다.
}
}
코드 설명:
BeanOutputParser<CityInfo> parser = new BeanOutputParser<>(CityInfo.class);:CityInfo클래스로 파싱할BeanOutputParser를 생성합니다.parser.getFormat(): 이 메서드는 LLM에게 "나는 이런 JSON 형식으로 응답을 원해"라고 알려주는 지시문을 자동으로 생성합니다. 예를 들어,{"cityName": "string", "weather": "string", "temperature": "string"}와 같은 형태의 설명을 프롬프트에 추가합니다.chatClient.prompt()...content().parse(parser): LLM으로부터 텍스트 응답을 받은 후,.parse(parser)를 호출하여BeanOutputParser가 텍스트를CityInfo객체로 변환하도록 지시합니다. Spring Boot의 기본 JSON 직렬화 덕분에, 이CityInfo객체는 웹 응답으로 자동으로 JSON 형태로 반환됩니다.
4.2.2. ListOutputParser를 이용한 리스트 응답 파싱
LLM에게 여러 개의 항목으로 구성된 리스트 형태의 응답을 요청하고 싶을 때 ListOutputParser를 사용할 수 있습니다. 예를 들어, 추천 목록이나 키워드 목록을 받을 때 유용합니다.
예제: 영화 추천 리스트 파싱
사용자가 좋아하는 장르를 입력하면, LLM이 해당 장르의 영화 3편을 추천하고, 그 리스트를 자바 List<String>으로 파싱해봅시다.
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.ListOutputParser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class ListOutputParserController {
private final ChatClient chatClient;
public ListOutputParserController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* ListOutputParser를 사용하여 LLM의 리스트 응답을 자바 List<String>으로 파싱합니다.
* 예: http://localhost:8080/recommendmovies?genre=SF
* @param genre 추천받고 싶은 영화 장르
* @return LLM이 추천한 영화 제목 리스트
*/
@GetMapping("/recommendmovies")
public List<String> recommendMovies(@RequestParam(value = "genre", defaultValue = "코미디") String genre) {
// 1. ListOutputParser 생성
ListOutputParser parser = new ListOutputParser();
// 2. 프롬프트 템플릿 정의: LLM에게 리스트 형식의 응답을 요청합니다.
String promptString = """
'{genre}' 장르의 영화 3편을 추천해줘. 각 영화는 쉼표로 구분하고, 다른 설명 없이 영화 제목만 나열해줘.
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString, Map.of("genre", genre, "format", parser.getFormat()));
// 3. LLM 호출 및 응답 파싱
return chatClient.prompt()
.user(promptTemplate.render())
.call()
.content()
.parse(parser); // ListOutputParser를 사용하여 LLM의 응답을 List<String>으로 파싱합니다.
}
}
코드 설명:
ListOutputParser parser = new ListOutputParser();:ListOutputParser를 생성합니다. 이 파서는 쉼표(,)로 구분된 문자열을List<String>으로 변환하는 기본 동작을 가집니다.parser.getFormat():ListOutputParser가 기대하는 출력 형식을 LLM에게 알려주는 지시문을 생성합니다. 예를 들어, "Use a comma as a delimiter."와 같은 내용이 포함될 수 있습니다.- LLM에게 쉼표로 구분된 영화 제목만 나열하도록 명확하게 지시하는 것이 중요합니다.
.parse(parser)를 통해 LLM 응답 텍스트를List<String>객체로 변환합니다.
이처럼 BeanOutputParser와 ListOutputParser를 포함한 다양한 출력 파서는 Spring AI LLM 개발 과정에서 LLM의 자유로운 응답을 애플리케이션에서 다루기 쉬운 구조화된 데이터로 변환하는 데 필수적인 도구입니다. Spring AI 예제를 통해 프롬프트 엔지니어링과 출력 파서의 중요성과 활용법을 익히면, 여러분은 훨씬 더 강력하고 실용적인 AI 애플리케이션을 구축할 수 있을 것입니다.
5. 실전 활용 사례: 챗봇, 요약, 코드 생성 애플리케이션 예제
이제 Spring AI의 기본 개념과 고급 기능을 익혔으니, 실제 업무나 일상생활에서 유용하게 활용될 수 있는 애플리케이션 사례들을 살펴보겠습니다. 이 섹션에서는 Spring AI를 활용하여 챗봇, 텍스트 요약, 코드 생성 등의 기능을 구현하는 구체적인 애플리케이션 개발 예시를 제시합니다. Spring AI 챗봇 개발은 물론, 다양한 Spring AI LLM 개발 시나리오를 통해 Spring AI의 실용성을 경험할 수 있을 것입니다.
5.1. 실시간 챗봇 애플리케이션 구현
가장 대표적인 LLM 활용 사례는 바로 챗봇입니다. Spring AI를 사용하면 백엔드 챗봇 로직을 매우 쉽게 구현할 수 있습니다. 여기서는 간단한 대화형 챗봇을 만들어보겠습니다. 사용자와 AI 간의 대화 기록을 유지하며, 이전 대화 내용을 기반으로 응답하는 기능을 포함합니다.
이 예제에서는 간단한 웹 인터페이스를 만들고, 사용자의 질문을 받아 LLM에 전달한 후 응답을 화면에 표시합니다. 대화 기록을 저장하기 위해 간단한 List를 사용하겠습니다. 실제 프로덕션 환경에서는 데이터베이스나 캐시를 사용하는 것이 일반적입니다.
먼저, 대화 메시지를 저장할 DTO(Data Transfer Object) 클래스를 정의합니다.
// src/main/java/com/example/springaihelloworld/ChatMessage.java
package com.example.springaihelloworld;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.ai.chat.prompt.messages.Message;
import org.springframework.ai.chat.prompt.messages.MessageType;
@Data
@AllArgsConstructor
@NoArgsConstructor // Lombok을 사용하여 기본 생성자, 모든 필드를 포함하는 생성자, Getter/Setter 등을 자동 생성합니다.
public class ChatMessage {
private String role; // "user" 또는 "assistant" (AI의 경우)
private String content;
// Spring AI의 Message 객체로 변환하는 유틸리티 메서드
public Message toSpringAIMessage() {
if ("user".equalsIgnoreCase(role)) {
return new org.springframework.ai.chat.prompt.messages.UserMessage(content);
} else if ("assistant".equalsIgnoreCase(role)) {
return new org.springframework.ai.chat.prompt.messages.AssistantMessage(content);
}
return new org.springframework.ai.chat.prompt.messages.SystemMessage(content); // 기본값 또는 다른 역할 처리
}
}
이제 챗봇 컨트롤러와 간단한 HTML 페이지를 만들어보겠습니다.
// src/main/java/com/example/springaihelloworld/ChatbotController.java
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatPromptTemplate;
import org.springframework.ai.chat.prompt.messages.Message;
import org.springframework.ai.chat.prompt.messages.UserMessage;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
@Controller // 이 클래스가 웹 페이지를 렌더링하는 컨트롤러임을 나타냅니다.
public class ChatbotController {
private final ChatClient chatClient;
// 대화 기록을 저장할 리스트 (임시, 실제 앱에서는 DB/캐시 사용)
private final List<ChatMessage> conversationHistory = new ArrayList<>();
public ChatbotController(ChatClient chatClient) {
this.chatClient = chatClient;
// 초기 시스템 메시지 추가 (선택 사항)
conversationHistory.add(new ChatMessage("system", "너는 친절하고 유용한 AI 비서야."));
}
/**
* 챗봇 웹 페이지를 보여줍니다.
* @param model Thymeleaf 템플릿에 데이터를 전달하기 위한 모델 객체
* @return 템플릿 파일 이름 (chat.html)
*/
@GetMapping("/chatbot")
public String showChatbot(Model model) {
model.addAttribute("messages", conversationHistory);
return "chat"; // src/main/resources/templates/chat.html 파일을 렌더링합니다.
}
/**
* 사용자로부터 메시지를 받아 LLM과 대화하고, 응답을 대화 기록에 추가합니다.
* @param userMessage 사용자 입력 메시지
* @return 챗봇 페이지로 리다이렉트
*/
@PostMapping("/chatbot")
public String chatWithBot(@RequestParam("message") String userMessage) {
// 1. 사용자 메시지를 대화 기록에 추가
conversationHistory.add(new ChatMessage("user", userMessage));
// 2. 대화 기록을 Spring AI Message 객체 리스트로 변환
List<Message> springAIMessages = conversationHistory.stream()
.map(ChatMessage::toSpringAIMessage)
.toList();
// 3. ChatPromptTemplate 생성 (대화 기록을 포함)
ChatPromptTemplate chatPromptTemplate = ChatPromptTemplate.fromMessages(springAIMessages);
// 4. LLM 호출 및 응답 받기
String aiResponseContent = chatClient.prompt()
.from(chatPromptTemplate)
.call()
.content();
// 5. AI 응답을 대화 기록에 추가
conversationHistory.add(new ChatMessage("assistant", aiResponseContent));
return "redirect:/chatbot"; // 다시 챗봇 페이지로 리다이렉트하여 업데이트된 대화 기록을 보여줍니다.
}
}
이제 챗봇을 위한 간단한 HTML 템플릿 파일을 src/main/resources/templates/chat.html 경로에 생성합니다. Thymeleaf 의존성을 pom.xml에 추가해야 합니다.
<!-- pom.xml -->
<dependencies>
<!-- ... 기존 의존성 ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<!-- src/main/resources/templates/chat.html -->
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Spring AI 챗봇</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; }
.chat-container { max-width: 800px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.message-list { height: 400px; overflow-y: scroll; border: 1px solid #ddd; padding: 10px; margin-bottom: 20px; border-radius: 4px; background-color: #f9f9f9; }
.message { margin-bottom: 10px; padding: 8px 12px; border-radius: 6px; }
.message.user { background-color: #e0f7fa; text-align: right; }
.message.assistant { background-color: #ffe0b2; text-align: left; }
.message.system { background-color: #e6e6e6; text-align: center; font-style: italic; color: #555; }
.chat-form { display: flex; }
.chat-form input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; }
.chat-form button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; }
.chat-form button:hover { background-color: #0056b3; }
</style>
</head>
<body>
<div class="chat-container">
<h1>Spring AI 챗봇</h1>
<div class="message-list">
<div th:each="msg : ${messages}" th:classappend="${msg.role}" class="message">
<strong th:text="${msg.role == 'user' ? '나' : (msg.role == 'assistant' ? 'AI' : '시스템')}"></strong>: <span th:text="${msg.content}"></span>
</div>
</div>
<form th:action="@{/chatbot}" method="post" class="chat-form">
<input type="text" name="message" placeholder="메시지를 입력하세요..." required/>
<button type="submit">전송</button>
</form>
</div>
</body>
</html>
애플리케이션을 실행하고 http://localhost:8080/chatbot으로 접속하면, 대화형 챗봇을 경험할 수 있습니다. Spring AI 챗봇 개발은 이렇게 대화 기록을 효과적으로 관리하고, 이를 LLM에게 전달하는 것이 핵심입니다.
5.2. 텍스트 요약 애플리케이션
대량의 텍스트에서 핵심 내용을 빠르게 파악하는 것은 매우 중요한 기능입니다. Spring AI를 사용하면 강력한 텍스트 요약기를 쉽게 만들 수 있습니다.
// src/main/java/com/example/springaihelloworld/SummarizationController.java
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class SummarizationController {
private final ChatClient chatClient;
public SummarizationController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 텍스트 요약 기능을 제공합니다.
* HTTP POST 요청으로 텍스트를 받아 LLM으로 요약합니다.
* @param requestBody JSON 요청 본문 (예: {"text": "여기에 긴 텍스트를 입력하세요."})
* @return 요약된 텍스트
*/
@PostMapping("/summarize-text")
public String summarizeText(@RequestBody Map<String, String> requestBody) {
String originalText = requestBody.get("text");
if (originalText == null || originalText.trim().isEmpty()) {
return "요약할 텍스트를 제공해주세요.";
}
// 프롬프트 템플릿 정의: 요약할 텍스트와 요약 길이 지시 포함
String template = """
다음 텍스트를 3문장 이내로 핵심 내용만 요약해줘.
텍스트:
{text}
""";
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("text", originalText));
// LLM 호출 및 응답 반환
return chatClient.prompt()
.user(promptTemplate.render())
.call()
.content();
}
}
테스트 방법:
Postman이나 curl 같은 도구를 사용하여 http://localhost:8080/summarize-text로 POST 요청을 보냅니다.
- Method:
POST - Headers:
Content-Type: application/json - Body (raw JSON):
요청을 보내면, LLM이 텍스트를 요약하여 응답할 것입니다.{ "text": "스프링 AI는 최근 출시된 스프링 프레임워크 기반의 인공지능 개발 도구입니다. 이 도구는 자바 개발자들이 대규모 언어 모델(LLM)을 활용하여 다양한 지능형 애플리케이션을 쉽게 구축할 수 있도록 설계되었습니다. OpenAI, Hugging Face, Azure OpenAI 등 여러 LLM 제공업체를 추상화하여 하나의 통일된 인터페이스로 제공하며, 프롬프트 엔지니어링, 출력 파서, 임베딩 및 벡터 데이터베이스 통합과 같은 강력한 기능들을 포함하고 있습니다. 이를 통해 개발자는 LLM API의 복잡한 구현에 신경 쓸 필요 없이, 비즈니스 로직에 집중하여 AI 기능을 신속하게 통합할 수 있습니다. 특히, Spring Boot의 자동 설정 기능을 활용하여 몇 줄의 코드만으로 LLM 연동이 가능하며, 이는 자바 개발자들이 AI 시대에 발맞춰 나갈 수 있도록 돕는 중요한 전환점이 될 것으로 기대됩니다. 스프링 AI는 엔터프라이즈 환경에서 검증된 스프링의 안정성과 확장성을 바탕으로, AI 애플리케이션 개발의 새로운 표준을 제시하고 있습니다." }
5.3. 코드 생성 및 설명 애플리케이션
LLM은 프로그래밍 코드 생성, 버그 수정, 코드 설명 등 개발 작업에도 큰 도움을 줄 수 있습니다. Spring AI를 활용하여 간단한 코드 생성/설명 도구를 만들어보겠습니다.
// src/main/java/com/example/springaihelloworld/CodeAssistantController.java
package com.example.springaihelloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class CodeAssistantController {
private final ChatClient chatClient;
public CodeAssistantController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 코드 생성 요청을 처리합니다.
* @param requestBody JSON 요청 본문 (예: {"language": "Java", "task": "두 수를 더하는 함수를 만들어줘"})
* @return 생성된 코드
*/
@PostMapping("/generate-code")
public String generateCode(@RequestBody Map<String, String> requestBody) {
String language = requestBody.get("language");
String task = requestBody.get("task");
if (language == null || task == null || language.trim().isEmpty() || task.trim().isEmpty()) {
return "언어와 작업 내용을 모두 제공해주세요.";
}
String template = """
'{language}' 언어로 다음 작업을 수행하는 코드를 생성해줘:
'{task}'
코드만 제공하고 다른 설명은 제외해줘.
""";
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("language", language, "task", task));
return chatClient.prompt()
.user(promptTemplate.render())
.call()
.content();
}
/**
* 코드 설명을 요청합니다.
* @param requestBody JSON 요청 본문 (예: {"code": "public class MyClass { ... }"})
* @return 코드에 대한 설명
*/
@PostMapping("/explain-code")
public String explainCode(@RequestBody Map<String, String> requestBody) {
String code = requestBody.get("code");
if (code == null || code.trim().isEmpty()) {
return "설명할 코드를 제공해주세요.";
}
String template = """
다음 코드를 자세히 설명해줘:
```
{code}
```
""";
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("code", code));
return chatClient.prompt()
.user(promptTemplate.render())
.call()
.content();
}
}
테스트 방법 (Postman 또는 curl):
- 코드 생성:
http://localhost:8080/generate-code로 POST 요청- Headers:
Content-Type: application/json - Body (raw JSON):
{ "language": "Python", "task": "두 개의 숫자를 입력받아 곱셈 결과를 반환하는 함수" }
- 코드 설명:
http://localhost:8080/explain-code로 POST 요청- Headers:
Content-Type: application/json - Body (raw JSON):
{ "code": "public String hello(String name) { return \"Hello, \" + name + \"!\"; }" }
이처럼 Spring AI는 Spring AI LLM 개발을 통해 여러분의 애플리케이션에 지능적인 챗봇, 효율적인 정보 요약, 생산적인 코드 보조와 같은 다양한 기능을 쉽게 추가할 수 있도록 돕습니다. 이 예제들은 단순한 시작점일 뿐이며, 여러분의 창의적인 아이디어를 더해 무궁무진한 AI 애플리케이션을 만들 수 있을 것입니다.
6. Spring AI 개발의 베스트 프랙티스 및 다음 단계: 전문가를 위한 심화 가이드
지금까지 Spring AI를 활용한 LLM 애플리케이션 개발의 기초부터 실제 활용 사례까지 폭넓게 다루었습니다. 이제 마지막 섹션에서는 실제 프로덕션 환경에서 Spring AI 애플리케이션을 개발하고 운영할 때 고려해야 할 모범 사례(Best Practices), 성능 최적화 팁, 그리고 지속적인 학습을 위한 추가 리소스를 안내합니다. 이 내용은 기본적인 개발 지식을 넘어, Spring 프레임워크에 익숙한 전문 개발자분들이 더욱 견고하고 효율적인 Spring AI LLM 개발을 할 수 있도록 돕기 위함입니다.
6.1. Spring AI 애플리케이션 개발의 모범 사례 (Best Practices)
6.1.1. API 키 보안 및 관리
가장 중요하고 기본적인 사항입니다. LLM API 키는 여러분의 계정으로 서비스에 접근하고 비용을 발생시킬 수 있는 민감한 정보입니다.
- 환경 변수 사용: 절대 코드에 API 키를 하드코딩하지 마십시오.
application.properties에서도 직접 입력하는 것보다spring.ai.openai.api-key=${OPENAI_API_KEY}와 같이 환경 변수를 참조하는 것이 좋습니다. - 시크릿 관리 솔루션: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault와 같은 전용 시크릿 관리 솔루션을 사용하여 API 키를 안전하게 저장하고 애플리케이션에 주입하세요. Spring Cloud Config와 같은 도구와 통합하여 더욱 강력한 시크릿 관리를 할 수 있습니다.
6.1.2. 비용 관리 및 모니터링
LLM API는 사용량에 따라 비용이 발생합니다. 예기치 않은 비용 폭탄을 피하기 위해 다음 사항들을 고려해야 합니다.
- 토큰 사용량 모니터링: 각 LLM 호출 시 사용되는 입력 및 출력 토큰 수를 모니터링하고 기록하세요. Spring AI는
ChatResponse객체에 토큰 사용량 정보를 포함하고 있습니다. - 최대 토큰 제한:
spring.ai.openai.chat.options.max-tokens와 같은 옵션을 사용하여 LLM이 생성하는 응답의 최대 길이를 제한하여 불필요한 비용 발생을 막으세요. - 캐싱 전략: 동일하거나 유사한 프롬프트에 대한 응답은 캐싱하여 불필요한 LLM 호출을 줄이세요. Spring Cache Abstraction을 활용하거나 Redis와 같은 외부 캐시 시스템을 통합할 수 있습니다.
- 요청 제한(Rate Limiting): 너무 많은 요청이 LLM API로 동시에 전송되지 않도록 클라이언트 측 또는 API 게이트웨이에서 요청 제한을 설정하세요.
6.1.3. 견고한 에러 핸들링 및 재시도 로직
LLM API 호출은 네트워크 문제, 서비스 과부하, 잘못된 프롬프트 등으로 인해 실패할 수 있습니다.
- 예외 처리: LLM 호출 시 발생할 수 있는
SpringAiClientException과 같은 예외를 적절히 처리하고 사용자에게 의미 있는 피드백을 제공하세요. - 재시도(Retry) 메커니즘: 일시적인 네트워크 문제나 서비스 장애에 대비하여, 실패한 LLM 호출에 대한 재시도 로직을 구현하세요. Spring Retry와 같은 라이브러리를 활용하면 간편하게 재시도 정책을 적용할 수 있습니다. 지수 백오프(Exponential Backoff) 전략을 사용하는 것이 일반적입니다.
6.1.4. 프롬프트 버전 관리 및 테스트
프롬프트는 LLM 애플리케이션의 핵심 로직과 같습니다.
- 버전 관리: Git과 같은 버전 관리 시스템을 사용하여 프롬프트 템플릿을 관리하세요.
- 테스트 자동화: 프롬프트 변경이 LLM 응답에 미치는 영향을 평가하기 위한 자동화된 테스트를 작성하세요. 특정 입력에 대해 LLM이 예상한 응답을 생성하는지 검증하는 테스트는 매우 중요합니다.
- 실험 및 A/B 테스트: 다양한 프롬프트 전략이나 LLM 모델을 실험하고, 실제 사용자 데이터를 기반으로 성능을 비교하여 최적의 프롬프트를 찾아내세요.
6.2. 성능 최적화 팁
LLM 호출은 일반적으로 네트워크 지연과 컴퓨팅 비용이 높으므로, 성능 최적화는 매우 중요합니다.
- 비동기 처리: 사용자가 LLM 응답을 기다리는 동안 UI가 멈추지 않도록, LLM 호출을 비동기적으로 처리하세요. Spring의
CompletableFuture나Project Reactor(WebFlux 사용 시)를 활용하여 논블로킹(non-blocking) 방식으로 처리할 수 있습니다. - 스트리밍 응답 활용: LLM이 응답을 한 번에 보내지 않고 토큰 단위로 스트리밍하는 경우, Spring AI의 스트리밍 기능을 활용하여 사용자에게 즉각적인 피드백을 제공하고 전체 응답 대기 시간을 줄일 수 있습니다.
chatClient.prompt()...stream().content()와 같은 방식으로 스트리밍 응답을 처리할 수 있습니다. - 프롬프트 최적화: 불필요하게 긴 프롬프트는 LLM의 처리 시간을 늘리고 비용을 증가시킵니다. 필요한 정보만 간결하게 포함하도록 프롬프트를 최적화하세요.
- 모델 선택: 애플리케이션의 요구사항에 따라 가장 적절한 모델을 선택하세요. 최신 대규모 모델이 항상 최적의 선택은 아닐 수 있습니다. 비용 효율적이거나 더 빠른 응답 시간을 제공하는 소형 모델도 좋은 대안이 될 수 있습니다. 예를 들어,
gpt-3.5-turbo는gpt-4o보다 빠르고 저렴합니다.
6.3. 다음 단계: Spring AI의 심화 활용 및 확장
지금까지 배운 내용을 바탕으로 Spring AI LLM 개발의 다음 단계를 탐색해봅시다.
6.3.1. 임베딩(Embedding) 및 벡터 데이터베이스 (RAG)
단순히 LLM을 호출하는 것을 넘어, 여러분의 애플리케이션에 특정 도메인 지식이나 최신 정보를 제공하고 싶다면 검색 증강 생성(RAG, Retrieval Augmented Generation) 패턴을 고려해야 합니다.
- 임베딩 모델: Spring AI는 텍스트를 고차원 벡터(숫자 배열)로 변환하는 임베딩 모델을 제공합니다. 이 벡터는 텍스트의 의미론적 유사성을 나타냅니다.
- 벡터 데이터베이스: 임베딩된 텍스트 데이터를 저장하고 검색하기 위해 Qdrant, Chroma, PGVector 등 벡터 데이터베이스를 사용합니다.
- RAG 흐름:
- 사용자 질문이 들어오면, 질문을 임베딩합니다.
- 임베딩된 질문과 유사한 의미를 가진 문서를 벡터 데이터베이스에서 검색합니다.
- 검색된 문서를 사용자 질문과 함께 LLM에게 프롬프트로 전달하여 답변을 생성합니다. (즉, LLM은 검색된 문서를 "참고"하여 답변합니다.)
Spring AI는 EmbeddingClient 인터페이스와 다양한 벡터 데이터베이스 통합을 통해 RAG 패턴을 쉽게 구현할 수 있도록 지원합니다. 이는 LLM의 환각(Hallucination) 현상을 줄이고, 특정 도메인에 특화된 정확한 답변을 얻는 데 매우 효과적입니다.
6.3.2. Function Calling (도구 사용)
LLM은 종종 외부 도구(예: 날씨 API, 데이터베이스 쿼리, 검색 엔진)를 호출하여 정보를 얻거나 특정 작업을 수행해야 할 때가 있습니다. Function Calling 또는 Tool Use는 LLM이 이러한 외부 도구를 언제, 어떤 인자와 함께 호출해야 할지 스스로 결정하도록 돕는 기능입니다.
Spring AI는 LLM이 호출할 수 있는 자바 함수를 쉽게 정의하고, 이를 LLM에 노출하는 기능을 제공합니다. 이를 통해 LLM 기반 에이전트(Agent)를 개발하여 더욱 복잡하고 동적인 작업을 수행할 수 있습니다. 예를 들어, 사용자가 "다음 주 서울 날씨 알려줘"라고 질문하면, LLM이 날씨 API를 호출하는 함수를 식별하고, 필요한 인자(서울, 다음 주)를 추출하여 함수를 호출한 후, 그 결과를 바탕으로 답변을 생성하는 방식으로 동작합니다.
6.3.3. Spring AI 최신 정보 및 커뮤니티 참여
Spring AI 프로젝트는 빠르게 발전하고 있습니다. 최신 정보를 얻고 커뮤니티에 참여하는 것은 지속적인 학습과 문제 해결에 큰 도움이 됩니다.
- 공식 문서: Spring AI 공식 문서는 가장 정확하고 최신 정보를 제공합니다. 새로운 기능이나 변경 사항을 항상 확인하세요.
- GitHub 저장소: Spring AI GitHub 저장소를 통해 소스 코드를 탐색하고, 이슈를 보고하며, 커뮤니티에 기여할 수 있습니다.
- Spring 블로그 및 컨퍼런스: Spring 공식 블로그나 SpringOne과 같은 컨퍼런스를 통해 Spring AI의 최신 업데이트와 활용 사례를 접할 수 있습니다.
- 커뮤니티 포럼: Stack Overflow, Spring 커뮤니티 포럼 등에서 질문하고 다른 개발자들과 지식을 공유하세요.
자바 AI 개발의 여정은 이제 막 시작되었습니다. Spring AI는 이 여정을 훨씬 더 쉽고 즐겁게 만들어 줄 강력한 도구입니다. 이 글에서 제시된 Spring AI 예제와 Spring AI 사용법, 그리고 Spring AI 프롬프트 엔지니어링 및 Spring AI 챗봇 등 다양한 실전 활용 팁을 바탕으로 여러분의 창의적인 아이디어를 현실로 만들어나가시길 바랍니다. Spring AI와 함께 지능형 애플리케이션 개발의 무한한 가능성을 탐험하세요!
'DEV > Spring 3.0' 카테고리의 다른 글
| 오브젝트와 의존관계 (0) | 2015.12.13 |
|---|---|
| 서버 에러코드 (0) | 2015.11.30 |
| 스프링(Spring) (0) | 2015.11.26 |
- Total
- Today
- Yesterday
- 코드생성AI
- Rag
- spring프레임워크
- 직구
- 데이터베이스
- 크로미움
- 해외
- Oracle
- restapi
- 배민
- 업무자동화
- 도커
- n8n
- 웹개발
- 자바AI개발
- springboot
- llm최적화
- AI솔루션
- springai
- selenium
- 비즈니스성장
- 생산성향상
- 펄
- 배민문방구
- 오픈소스DB
- 웹스크래핑
- 프롬프트엔지니어링
- 개발생산성
- Java
- SEO최적화
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |