JSCODE Logo
블로그후기멘토진
회사명 : JSCODE대표 : 박재성사업자 등록번호 : 244-22-01557통신판매업 : 제 2023-인천미추홀-0381 호
학원 명칭 : 제이에스코드(JSCODE)원격학원학원설립ㆍ운영 등록번호 : 제6063호

서울특별시 구로구 경인로 20가길 11(오류동, 아델리아)

Copyright ⓒ 2025 JSCODE - 최상위 현업 개발자들의 프로그래밍 교육 All rights reserved.

이용약관개인정보처리방침
← 블로그 목록으로 돌아가기

[실습] RAG Service 구현

JSCODE 시니
JSCODE 시니
2026. 06. 13.
author
JSCODE 시니
category
Spring AI
createdAt
Jun 13, 2026 09:53 AM
isPublic
isPublic
series
실무에 바로 적용하는 Spring AI: Spring 서비스에 챗봇·RAG·MCP 도입하기
slug
practice-implementing-rag-service
type
post
updatedAt

✅ 1. RAG Service 구현

RagChatService.java
public RagChatService(ChatClient.Builder chatClientBuilder, Advisor[] advisors) { this.chatClient = chatClientBuilder.defaultOptions(ChatOptions.builder().temperature(0.0)).defaultAdvisors(advisors).build(); }
  • 조금 더 사실적인 답변을 위하여 temperature를 0.0으로 설정
 
 
 

✅ 2. 메타데이터 필터링 적용

  • 보통 RAG에서 사용하는 '유사도 검색(Vector Search)'은 내용의 의미가 비슷한 것을 찾는 기술임
  • 하지만 이 유사도 검색은 생각보다 멍청해서 치명적인 단점이 있음
  • 필터가 없을 때(필터링 미적용 시)
    • 도서관에 있는 수백만 권의 모든 책 중에서 '배치 처리'라는 단어와 가장 의미가 비슷한 문장을 찾아줘!" ➔ 시간도 오래 걸리고, 엉뚱한 요리책의 '배추 처리' 문장을 가져올 수도 있음
  • 필터가 있을 때(필터링 적용시)
    • 먼저 책 표지에 category == 'Spring AI'이고 author == 'JSCODE Synee'라고 적힌 책들만 빼와. 그리고 그 안에서만 '배치 처리'랑 비슷한 걸 찾아!" ➔ 정확하고 빠름
  • 따라서 실무에서도 FilterExpression을 필수로 적용해야함!
    • 다중 사용자 환경에서의 '보안 및 데이터 격리’ (연봉계약서)
    • 할루시네이션(환각) 방지 및 정확도 극대화
    • 인프라 비용 절감과 검색 속도 향상(date >= '2026-01-01’)
RagChatService.java
private ChatClient.ChatClientRequestSpec prepareRequest(Prompt prompt, String conversationId, Optional<String> filterExpressionAsOpt){ return chatClient.prompt(prompt) .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId)) .advisors(advisorSpec -> advisorSpec.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, filterExpressionAsOpt.orElse("") )); }
 
 
 

✅ 3. 전체 코드

package com.jscode.chat.service; @Service public class RagChatService { private final ChatClient chatClient; public RagChatService(ChatClient.Builder chatClientBuilder, Advisor[] advisors) { this.chatClient = chatClientBuilder.defaultOptions(ChatOptions.builder().temperature(0.0)).defaultAdvisors(advisors).build(); } public Flux<String> stream(Prompt prompt, String conversationId, Optional<String> filterExpressionAsOpt) { // 응답을 받아오는 코드 추가 return prepareRequest(prompt, conversationId, filterExpressionAsOpt) .stream() .content(); } public @Nullable ChatResponse call(Prompt prompt, String conversationId, Optional<String> filterExpressionAsOpt){ return prepareRequest(prompt, conversationId, filterExpressionAsOpt) .call() .chatResponse(); } private ChatClient.ChatClientRequestSpec prepareRequest(Prompt prompt, String conversationId, Optional<String> filterExpressionAsOpt){ return chatClient.prompt(prompt) .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId)) .advisors(advisorSpec -> advisorSpec.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, filterExpressionAsOpt.orElse("") )); } }