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

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

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

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

[실습] Chat Controller 구현 - call과 stream 방식의 Chat REST API

JSCODE 시니
JSCODE 시니
2026. 06. 13.
author
JSCODE 시니
category
Spring AI
createdAt
Jun 13, 2026 09:52 AM
isPublic
isPublic
series
실무에 바로 적용하는 Spring AI: Spring 서비스에 챗봇·RAG·MCP 도입하기
slug
practice-chat-controller-call-vs-stream
type
post
updatedAt
notion image

✅ 1. call() 메서드 생성

ChatService.java
public @Nullable ChatResponse call(Prompt prompt, String conversationId){ return prepareRequest(prompt, conversationId) .call() .chatResponse(); }
 
 
 

✅ 2. record 생성

💁‍♀️
record는 데이터를 담고 전달하는 목적에 최적화된 최신 자바 문법입니다. 소괄호 안에 필요한 데이터(필드)를 나열해주면, 자바가 알아서 생성자와 Getter를 모두 만들어줍니다!
주로 REST API에서 프론트엔드가 서버로 보내는 데이터를 JSON 형태로 받아내기 위해 DTO를 제작할 때 사용합니다.
record 문법이 생소하신 분들은 한 번 학습하고 넘어가시길 바랍니다.
ChatController.java
public record PromptBody(@NotEmpty String conversationId, @NotEmpty String userPrompt, @Nullable String systemPrompt, DefaultChatOptions chatOptions){}
 
 
 

✅ 3. call 방식 생성

ChatController.java
@PostMapping(value = "/call", produces = MediaType.APPLICATION_JSON_VALUE) public ChatResponse call(@RequestBody @Valid PromptBody promptBody) { // 1. 메시지들을 차곡차곡 담을 빈 리스트를 생성 List<Message> messages = new ArrayList<>(); // 2. systemPrompt가 입력으로 들어왔다면 리스트에 넣자 if(promptBody.systemPrompt() != null && !promptBody.systemPrompt().isBlank()){ messages.add(new SystemMessage(promptBody.systemPrompt())); } // 3. userPrompt는 필수 값이니 무조건 리스트에 넣자 messages.add(new UserMessage(promptBody.userPrompt())); // 4. 리스트에 담긴 메시지들로 프롬프트 조립하기 Prompt.Builder promptBuilder = Prompt.builder().messages(messages); // 5. 프론트엔드에서 보낸 chatOptions가 있다면 적용하기 if(promptBody.chatOptions() != null){ promptBuilder.chatOptions(promptBody.chatOptions()); } // 완성된 프롬프트와 conversationID를 가지고 서비스 메서드 호출 return chatService.call(promptBuilder.build(), promptBody.conversationId()); }
 
 
 

✅ 4. 코드 리팩토링

ChatController.java
@PostMapping(value = "/call", produces = MediaType.APPLICATION_JSON_VALUE) public ChatResponse call(@RequestBody @Valid PromptBody promptBody) { Prompt prompt = createPrompt(promptBody); // 완성된 프롬프트와 conversationID를 가지고 서비스 메서드 호출 return chatService.call(prompt, promptBody.conversationId()); } private static Prompt createPrompt(PromptBody promptBody) { // 1. 메시지들을 차곡차곡 담을 빈 List를 생성 List<Message> messages = new ArrayList<>(); // 2. systemPrompt가 입력으로 들어왔다면 리스트에 넣자 if(promptBody.systemPrompt() != null && !promptBody.systemPrompt().isBlank()){ messages.add(new SystemMessage(promptBody.systemPrompt())); } // 3. userPrompt는 필수 값이니 무조건 리스트에 넣자 messages.add(new UserMessage(promptBody.userPrompt())); // 4. 리스트에 담긴 메시지들로 프롬프트 조립하기 Prompt.Builder promptBuilder = Prompt.builder().messages(messages); // 5. 프론트엔드에서 보낸 chatOptions가 있다면 적용하기 if(promptBody.chatOptions() != null){ promptBuilder.chatOptions(promptBody.chatOptions()); } return promptBuilder.build(); }
 
 
 

✅ 5. stream 방식 생성하기

ChatController.java
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> stream(@RequestBody @Valid PromptBody promptBody){ Prompt prompt = createPrompt(promptBody); return chatService.stream(prompt, promptBody.conversationId()); }
 
 
 

✅ 6. 로깅 설정

spring: application: name: chat # cli: true ai: #[1] openAI 설정 openai: # 환경변수 값 api-key: ${OPENAI_API_KEY} chat: # 실제 사용할 대화형 AI의 모델명 model: openai/gpt-4.1-nano # GitHub Models 서버로 요청을 보냄 base-url: https://models.github.ai/inference # 세부 API 경로 completions-path: /chat/completions #[2] Ollama 설정 ollama: init: pull-model-strategy: when_missing chat: model: hf.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF logging: level: org.springframework.ai.chat.client.advisor: DEBUG
 
 
 

✅ 7. 테스트

notion image
notion image