프로그래밍 PROGRAMMING/자바 JAVA AND FRAMEWORKS

Spring AI로 RAG 파이프라인 구현하기

매운할라피뇨 2026. 4. 1. 08:50
반응형
Spring AI로 RAG 파이프라인 구현하기

Spring AI는 Spring 생태계에서 LLM(대규모 언어 모델) 기반 애플리케이션을 개발할 수 있도록 설계된 프레임워크입니다. 그중에서도 RAG(Retrieval-Augmented Generation) 파이프라인은 외부 문서를 벡터 스토어에 저장하고, 사용자 질의와 유사한 문서를 검색해 LLM에게 컨텍스트로 제공하는 패턴으로, 할루시네이션(hallucination)을 줄이고 도메인 특화 응답 품질을 높이는 데 효과적입니다. 이 글에서는 Spring AI 1.x 기준으로 RAG 파이프라인을 단계별로 구성하는 방법을 다룹니다.


RAG 파이프라인의 구조 이해

RAG는 크게 두 단계로 나뉩니다.

  1. Ingestion(문서 적재): 문서를 청크(chunk)로 분할하고 임베딩(embedding) 벡터로 변환해 벡터 스토어에 저장합니다.
  2. Retrieval + Generation(검색 및 생성): 사용자의 질의를 임베딩해 유사도 검색을 수행한 뒤, 검색 결과를 프롬프트에 삽입해 LLM이 응답을 생성합니다.

Spring AI는 이 두 단계를 DocumentReader, TextSplitter, EmbeddingModel, VectorStore, ChatClient 등의 추상화 계층으로 지원합니다. 각 컴포넌트는 인터페이스 기반으로 설계되어 있어 OpenAI, Azure OpenAI, Ollama 등 다양한 백엔드로 교체할 수 있습니다.


의존성 설정 및 벡터 스토어 구성

Spring Initializr에서 spring-ai-openai-spring-boot-starterspring-ai-pgvector-store-spring-boot-starter를 선택하거나, 아래와 같이 pom.xml에 직접 추가합니다.

<!-- pom.xml -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- OpenAI 연동 (Chat + Embedding) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    <!-- PGVector 벡터 스토어 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

application.yml에 API 키와 벡터 스토어 설정을 추가합니다.

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-3-small  # 비용 효율적인 소형 모델
  datasource:
    url: jdbc:postgresql://localhost:5432/ragdb
    username: postgres
    password: ${DB_PASSWORD}

PGVector를 사용하려면 PostgreSQL에 pgvector 확장이 활성화되어 있어야 합니다. Spring AI Auto-configuration이 활성화되면 VectorStore 빈이 자동으로 등록됩니다.


문서 적재(Ingestion) 파이프라인 구현

문서를 읽어 청크로 분할하고 벡터 스토어에 저장하는 과정입니다. TokenTextSplitter는 토큰 수 기반으로 청크를 나누며, 청크 크기와 오버랩(overlap)을 조정해 검색 품질을 개선할 수 있습니다.

@Service
@RequiredArgsConstructor
public class DocumentIngestionService {

    private final VectorStore vectorStore;

    public void ingest(Resource resource) {
        // 1. PDF 또는 텍스트 파일 읽기
        List<Document> rawDocs = new PagePdfDocumentReader(resource).get();

        // 2. 청크 분할 — 512 토큰, 64 토큰 오버랩
        TextSplitter splitter = new TokenTextSplitter(512, 64, 5, 10000, true);
        List<Document> chunks = splitter.apply(rawDocs);

        // 3. 임베딩 생성 후 벡터 스토어에 저장
        vectorStore.add(chunks);

        System.out.println("적재 완료: " + chunks.size() + "개 청크");
    }
}
적재 완료: 47개 청크

PagePdfDocumentReader 외에도 TikaDocumentReader(다양한 문서 포맷 지원), JsonMetadataGenerator(메타데이터 보강) 등을 조합해 파이프라인을 확장할 수 있습니다.


검색 및 생성(RAG Query) 파이프라인 구현

Spring AI 1.x의 ChatClientadvisors API를 통해 RAG 동작을 선언적으로 구성할 수 있습니다. QuestionAnswerAdvisor는 사용자 질의를 가로채 벡터 스토어 검색 결과를 프롬프트에 자동으로 삽입합니다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/rag")
public class RagController {

    private final ChatClient.Builder chatClientBuilder;
    private final VectorStore vectorStore;

    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        // 유사도 높은 상위 5개 문서를 컨텍스트로 제공
        SearchRequest searchRequest = SearchRequest.defaults()
                .withTopK(5)
                .withSimilarityThreshold(0.75); // 0~1, 높을수록 엄격

        return chatClientBuilder.build()
                .prompt()
                .advisors(new QuestionAnswerAdvisor(vectorStore, searchRequest))
                .user(question)
                .call()
                .content();
    }
}

변경전 — 단순 Chat 호출 (컨텍스트 없음):

// 도메인 문서 없이 LLM 기본 지식에만 의존 → 할루시네이션 발생 가능
chatClient.prompt().user(question).call().content();

변경후QuestionAnswerAdvisor 적용:

// 벡터 스토어에서 관련 청크를 검색해 프롬프트에 자동 삽입
chatClient.prompt()
    .advisors(new QuestionAnswerAdvisor(vectorStore, searchRequest))
    .user(question).call().content();

similarityThreshold는 0.75 정도를 시작점으로 삼되, 실제 프로젝트의 문서 품질과 쿼리 패턴에 따라 조정하는 것을 권장합니다. 값이 너무 높으면 관련 문서가 누락될 수 있고, 너무 낮으면 노이즈가 증가합니다.


맺음말

Spring AI의 RAG 파이프라인은 DocumentReader → TextSplitter → VectorStore → QuestionAnswerAdvisor의 흐름으로 구성됩니다. 각 컴포넌트가 인터페이스 기반으로 추상화되어 있어, LLM 제공자나 벡터 스토어를 교체할 때 애플리케이션 코드 변경을 최소화할 수 있습니다.

운영 환경에서는 다음 사항을 추가로 고려할 필요가 있습니다.

  • 청크 크기 튜닝: 문서 유형(기술 문서 vs. 산문)에 따라 최적 청크 크기가 달라질 수 있습니다.
  • 메타데이터 필터링: FilterExpressionBuilder로 문서 출처, 날짜 범위 등을 조건으로 검색 범위를 좁히면 검색 정밀도를 높일 수 있습니다.
  • Re-ranking: 초기 검색 결과에 Cohere Rerank 등 교차 인코더를 적용해 최종 컨텍스트 품질을 개선할 수 있습니다.

Spring AI 공식 문서(docs.spring.io/spring-ai)에서 각 컴포넌트의 설정 옵션과 지원 모델 목록을 확인할 수 있습니다. RAG 패턴은 사내 지식 베이스 검색, 고객 지원 챗봇, 문서 기반 Q&A 등 다양한 영역에서 활용될 수 있습니다.

반응형