실무에 바로 적용하는 Spring AI: Spring 서비스에 챗봇·RAG·MCP 도입하기
practice-implementing-rag-service
✅ 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("")
));
}
}