LLM은 아무리 뛰어난 모델이라도 학습한 지식 안에서만 답변할 수 있습니다. 특정 기업의 사내 문서나 최신 법률처럼 외부 도메인 지식이 필요한 경우에는, 전혀 엉뚱한 답을 하거나 틀린 정보(Hallucination)를 말하기도 하죠.
이런 문제를 해결하기 위해 등장한 개념이 RAG(Retrieval-Augmented Generation)입니다. RAG는 외부 데이터를 검색해 LLM에게 맥락(Context)으로 제공함으로써, 보다 정확하고 신뢰할 수 있는 답변을 생성하도록 돕습니다.
그리고 이 복잡한 RAG 파이프라인을 Spring 기반으로 깔끔하게 구현할 수 있게 도와주는 프레임워크가 바로 Spring AI입니다. 이번 글에서는 Spring AI를 활용해 나만의 RAG 챗봇을 만드는 전체 구조와 컴포넌트 설계 방법을 소개합니다.
RAG 파이프라인 구성
Spring AI로 구성할 수 있는 대표적인 RAG 흐름은 다음과 같습니다.
1단계: 데이터 색인 (Data Indexing)
1.
문서 청킹(Chunking)
•
PDF 또는 텍스트 문서 업로드
•
텍스트 추출 및 정제
•
LLM이 이해하기 좋은 단위로 청킹(Text Splitting)
2.
벡터화 및 저장
•
각 청크에 대해 임베딩 생성
•
메타데이터 추가
•
벡터 저장소에 저장
2단계: 질의응답 (Data Retrieval & Generation)
•
사용자의 질문을 벡터화
•
유사한 문서 청크 검색
•
검색된 문서를 LLM에게 컨텍스트로 전달
•
최종 응답 생성
Spring AI로 구현하면 어떤 점이 좋을까?
이러한 RAG 시스템을 직접 구현하려면 다음과 같은 구성 요소들을 하나하나 직접 작성해야 합니다.
•
텍스트 청킹 전략
•
임베딩 모델 연동 (OpenAI, HuggingFace 등)
•
벡터 저장소 연동 (PgVector, Qdrant 등)
•
메타데이터 기반 필터링
•
LLM 프롬프트 구성 및 요청 처리
하지만 Spring AI는 이 복잡한 요소들을 표준화된 컴포넌트로 추상화해줍니다.
덕분에 개발자는 LLM 서비스 구현 그 자체에 집중할 수 있고, 인프라 구현 부담은 크게 줄어듭니다.
RAG 핵심 컴포넌트
Spring AI에서 RAG 파이프라인을구성하는 핵심 컴포넌트는 다음과 같습니다.
EmbeddingModel – 텍스트를 벡터로 변환하는 인터페이스
LLM과 검색 시스템을 연결하는 첫 번째 단계는 임베딩입니다.
Spring AI는 EmbeddingModel 인터페이스를 통해 다양한 벤더의 임베딩 모델을 쉽게 연동할 수 있게 해줍니다.
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
EmbeddingResponse call(EmbeddingRequest request);
default float[] embed(String text)
float[] embed(Document document);
default List<float[]> embed(List<String> texts)
default EmbeddingResponse embedForResponse(List<String> texts)
default int dimensions()
}
Java
복사
VectorStore – 문서 벡터 저장 및 검색을 위한 인터페이스
벡터 저장소는 임베딩된 문서를 저장하고, 질문과 유사한 문서를 빠르게 검색해주는 핵심 컴포넌트입니다.
public interface VectorStore extends DocumentWriter {
default String getName() { ... };
void add(List<Document> documents);
void delete(List<String> idList);
void delete(Filter.Expression filterExpression);
default void delete(String filterExpression) { ... };
List<Document> similaritySearch(String query);
List<Document> similaritySearch(SearchRequest request);
}
Java
복사
예를 들어, 처음에는 인메모리 기반의 SimpleVectorStore로 시작했다가
서비스 확장 시 PgVector, Pinecone, Milvus 등 외부 DB로 손쉽게 전환할 수 있습니다.
VectorStore prodStore = PgVectorStore.builder(jdbcTemplate, model)
.dimensions(1536)
.distanceType(PgDistanceType.COSINE_DISTANCE)
.build();
Java
복사
TokenTextSplitter - 텍스트 분할 전략 내장
LLM은 긴 문서를 한 번에 처리하지 못하기 때문에, 문서를 적절한 길이로 나누는 작업이 필요합니다.
Spring AI는 이를 위해 TextSplitter를 기본으로 제공합니다.
TextSplitter splitter = new TokenTextSplitter()
List<Document> chunks = splitter.split(document);
Java
복사
FilterExpression - 메타데이터 기반 검색 필터링
단순히 유사한 문서를 찾는 것만으로는 부족할 수 있습니다. Spring AI는 문서 메타데이터를 기반으로 조건 필터링을 지원합니다. SQL의 WHERE 절과 유사한 방식으로 필터링할 수 있습니다. 예를 들어 “2023년 이후 계약서”처럼 조건을 추가하여 더욱 정확한 검색이 가능합니다.
SearchRequest request = SearchRequest.builder()
.query("계약 기간은 언제야?")
.filterExpression("type == 'contract' AND year >= 2023")
.build();
List<Document> results = vectorStore.similaritySearch(request);
Java
복사
SQL의 WHERE 절과 유사한 방식으로 필터링할 수 있습니다.
직접 구현 vs Spring AI
항목 | 직접 구현 시 | Spring AI 사용 시 |
임베딩 연동 | OpenAI API 직접 호출 | EmbeddingModel 설정만 |
벡터 저장소 | PgVector 연동 및 SQL 구현 | VectorStore 추상화 |
텍스트 청크 처리 | 분할 로직 직접 작성 | TextSplitter 제공 |
메타데이터 필터링 | WHERE 절 직접 구성 | filterExpression 지원 |
벤더 교체 | 코드 전체 변경 | 설정만 변경 |
실습을 위한 간단한 API 구성
아래는 실제 프로젝트에 적용할 수 있는 API 구성입니다.
POST /api/v1/rag/documents
→ 문서를 업로드하고 임베딩 후 저장
POST /api/v1/rag/query
→ 사용자의 질문을 받아 검색 및 답변 생성
Bash
복사
마무리
RAG는 신뢰도 높은 LLM 서비스를 만들기 위한 현실적인 대안이며, Spring AI는 이를 구현하는 데 필요한 대부분의 요소들을 이미 잘 추상화된 상태로 제공합니다.
복잡한 파이프라인을 직접 만들 필요 없이, Spring의 익숙한 개발 패턴 안에서 강력한 RAG 시스템을 빠르게 구성할 수 있다는 점은 매우 큰 장점입니다.
다음 실습에서는 이론에서 소개한 컴포넌트를 실제 코드로 구성하며 실전에서 바로 활용할 수 있는 챗봇을 만들어보겠습니다.