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

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

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

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

[실습] RAG CLI 구현 및 실행 테스트

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-and-testing-rag-cli
type
post
updatedAt

✅ 1. application.yaml 수정

app: rag: documents-location-pattern: classpath:spring-ai-llm.pdf cli: enabled: true
 
 
 

✅ 2. chatConfig 수정

package com.jscode.chat; @Configuration public class ChatConfig { @Bean public SimpleLoggerAdvisor simpleLoggerAdvisor(){ // builder().build() 패턴을 사용하여 로거 객체 생성 return SimpleLoggerAdvisor.builder().build(); } @Bean public ChatMemory chatMemory(){ return MessageWindowChatMemory.builder().maxMessages(10).build(); } @Bean public MessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory chatMemory){ return MessageChatMemoryAdvisor.builder(chatMemory).build(); } @ConditionalOnProperty(prefix = "app.cli", name = "enabled", havingValue = "true") @Bean // 스프링 부트 서버가 완전히 켜지기 전에 단 한번 자동으로 실행 public CommandLineRunner cli(@Value("${spring.application.name}") String applicationName, RagChatService ragChatService, @Value("${app.cli.filter-expression:}") String filterExpression) { return args -> { // 1. 스프링 기본 로그 끄기 (채팅에 방해되지 않도록) LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); context.getLogger("ROOT").detachAppender("CONSOLE"); System.out.println("======================================="); System.out.println("🤖 [" + applicationName + "] CLI 챗봇을 시작합니다!"); System.out.println(" (종료하려면 'exit' 또는 'quit' 입력)"); System.out.println("======================================="); try (Scanner scanner = new Scanner(System.in)) { while (true) { System.out.print("\nUSER: "); String userMessage = scanner.nextLine(); // 2. 대화 종료 조건 (무한 루프 탈출) if (userMessage.equalsIgnoreCase("exit") || userMessage.equalsIgnoreCase("quit")) { System.out.println("대화를 종료합니다. 안녕히 계세요!"); break; } System.out.print("ASSISTANT: "); // 3.스트리밍 처리 (핵심 변경 포인트) // Flux(스트림)를 toIterable()로 바꾸면 일반적인 for-each 문으로 한 글자씩 꺼내 쓸 수 있음! Iterable<String> chatStream = ragChatService.stream( new Prompt(userMessage), "cli", // filterExpression의 값이 있을때만 통과 Optional.ofNullable(filterExpression).filter(s -> !s.isBlank()) ).toIterable(); for (String token : chatStream) { System.out.print(token); // 한 글자씩 화면에 출력 (타이핑 효과) } System.out.println(); // AI 대답이 끝나면 줄바꿈 한 번 } } }; } }
 
 
 

✅ 3. PostProcessor 등록

ChatConfig.java
@ConditionalOnProperty(prefix = "app.cli", name = "enabled", havingValue = "true") @Bean public DocumentPostProcessor printDocumentsPostProcessor() { return (query, documents) -> { System.out.println("\n[ Search Results ]"); System.out.println("==============================================="); if (documents == null || documents.isEmpty()) { System.out.println(" No search results found."); System.out.println("==============================================="); return documents; } for (int i = 0; i < documents.size(); i++) { Document document = documents.get(i); System.out.printf("▶ %d Document, Score: %.2f%n", i + 1, document.getScore()); System.out.println("-----------------------------------------------"); Optional.ofNullable(document.getText()).stream() .map(text -> text.split("\n")).flatMap(Arrays::stream) .forEach(line -> System.out.printf("%s%n", line)); System.out.println("==============================================="); } System.out.print("\n[ RAG 사용 응답 ]\n\n"); return documents; }; }
 
 
 

✅ 4. 후처리기 결합

@Bean public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor( VectorStore vectorStore, ChatClient.Builder chatClientBuilder, @Autowired(required = false) DocumentPostProcessor documentPostProcessor) { // 1. 문서 검색기 도구 설정 VectorStoreDocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.3) .topK(3) .build(); // 2. 다른 구성 요소들 생성 ContextualQueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder() .allowEmptyContext(true) .build(); MultiQueryExpander queryExpander = MultiQueryExpander.builder() .chatClientBuilder(chatClientBuilder) .build(); TranslationQueryTransformer queryTransformer = TranslationQueryTransformer.builder() .chatClientBuilder(chatClientBuilder) .targetLanguage("korean") .build(); // 3. 최종 합체 (RetrievalAugmentationAdvisor 빌더에서 후처리기 등록) RetrievalAugmentationAdvisor.Builder builder = RetrievalAugmentationAdvisor.builder() .documentRetriever(documentRetriever) .queryAugmenter(queryAugmenter) .queryExpander(queryExpander) .queryTransformers(queryTransformer); // 후처리기가 존재한다면 advisor 빌더에 추가 if (documentPostProcessor != null) { builder.documentPostProcessors(documentPostProcessor); } return builder.build(); }
 
 
 

✅ 5. 구현 및 테스트

notion image
notion image
notion image