실무에 바로 적용하는 Spring AI: Spring 서비스에 챗봇·RAG·MCP 도입하기
practice-tool-chat-config-and-controller-setup
✅ 1. CLI
ToolChatConfig.java
package com.jscode.tool;
@Configuration
public class ToolChatConfig {
@Bean
public SimpleLoggerAdvisor simpleLoggerAdvisor(){
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,
ToolService toolService,
@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: ");
Iterable<String> chatStream = toolService.stream(
"cli",
new Prompt(userMessage)
).toIterable();
for (String token : chatStream) {
System.out.print(token);
}
System.out.println();
}
}
};
}
}
✅ 2. RestAPI
ToolController.java
package com.jscode.tool.rest;
@RestController
@RequestMapping("/tool")
public class ToolController {
private final ToolService toolService;
public ToolController(ToolService toolService) {
this.toolService = toolService;
}
public record PromptBody(
@NotEmpty @Schema(description = "대화 식별자", example = "jscode-1234") String conversationId,
@NotEmpty @Schema(description = "사용자 입력 프롬프트", example = "안녕하세요, 제주도 날씨 어때요?") String userPrompt,
@Nullable @Schema(description = "시스템 프롬프트(선택)", example = "You are a helpful assistant.") String systemPrompt,
@Nullable @Schema(description = "채팅 옵션(선택)", implementation = DefaultChatOptions.class) DefaultChatOptions chatOptions
) {}
@PostMapping(value = "/call", produces = MediaType.APPLICATION_JSON_VALUE)
public ChatResponse call(@RequestBody @Valid PromptBody promptBody) {
Prompt prompt = createPrompt(promptBody);
// 완성된 프롬프트와 conversationID를 가지고 서비스 메소드 호출
return toolService.call(promptBody.conversationId, prompt);
}
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestBody @Valid PromptBody promptBody){
Prompt prompt = createPrompt(promptBody);
return toolService.stream(promptBody.conversationId, prompt);
}
private static Prompt createPrompt(PromptBody promptBody) {
List<Message> messages = new ArrayList<>();
if(promptBody.systemPrompt() != null && !promptBody.systemPrompt().isBlank()){
messages.add(new SystemMessage(promptBody.systemPrompt()));
}
messages.add(new UserMessage(promptBody.userPrompt()));
Prompt.Builder promptBuilder = Prompt.builder().messages(messages);
if(promptBody.chatOptions() != null){
promptBuilder.chatOptions(promptBody.chatOptions());
}
return promptBuilder.build();
}
}