관리 메뉴

The Nirsa Way

[Spring AI] #3-1. ChatService 분석 – 메시지 생성과 모델 호출 흐름 본문

Development/Spring AI

[Spring AI] #3-1. ChatService 분석 – 메시지 생성과 모델 호출 흐름

KoreaNirsa 2025. 6. 29. 22:32
반응형
※ 인프런 강의 Spring AI 실전 가이드: RAG 챗봇 만들기를 실습하는 내용입니다.
※ 해당 강의 코드를 코틀린 → 자바로 언어를 바꿔 진행하기 때문에 일부 코드 및 구현부가 다를 수 있습니다.
※ 실습이지만 코드를 직접 까보는 내용을 기록하는 포스팅이므로 강의 내용과 상이할 수 있습니다.

[Spring AI] #1. LLM 호출 실습 시작 – 구조 먼저 실행해보기
[Spring AI] #2. AiConfig 분석 – OpenAI API 연결 설정 방법
[Spring AI] #3-1. ChatService 분석 – 메시지 생성과 모델 호출 흐름
[Spring AI] #3-2. ChatService 분석 – buildRequestPrompt()로 요청 준비하기
[Spring AI] #3-3. ChatService 분석 – internalCall()로 요청과 응답 재구성 흐름 파악하기
[Spring AI] #4. ChatController 분석 – 요청 처리와 응답 흐름 정리

 

전체 코드 확인하기

전체적인 코드는 아래와 같으며 이후 포스팅에서는 "메시지 구성" 주석 부분에서부터 시작합니다.

package kr.co.ai.service;

import java.util.Arrays;
import java.util.List;

import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChatService {
    private final OpenAiApi openAiApi;

    /**
     * OpenAI 챗 API를 이용하여 응답을 생성합니다.
     *
     * @param userInput     사용자 입력 메시지
     * @param systemMessage 시스템 프롬프트
     * @param model         사용할 LLM 모델명
     * @return ChatResponse 객체 (실패 시 null)
     */
    public ChatResponse openAiChat(String userInput, String systemMessage, String model) {
        log.debug("OpenAI 챗 호출 시작 - 모델: {}", model);

        try {
            // 1. 메시지 구성
            List<Message> messages = Arrays.asList(
                    new SystemMessage(systemMessage),
                    new UserMessage(userInput)
            );

            // 2. 챗 옵션 구성
            OpenAiChatOptions chatOptions = OpenAiChatOptions.builder()
            		.model(model)
            	    .temperature(0.7d)
            	    .build();

            // 3. 프롬프트 생성
            Prompt prompt = new Prompt(messages, chatOptions);

            // 4. 챗 모델 생성 및 호출
            OpenAiChatModel chatModel = OpenAiChatModel.builder()
                    .openAiApi(openAiApi)
                    .build();

            return chatModel.call(prompt);

        } catch (Exception e) {
            log.error("OpenAI 챗 호출 중 오류 발생: {}", e.getMessage(), e);
            return null;
        }
    }
}

 


 

1. 메시지 구성 파악하기

메시지 구성하는 부분은 아래와 같은데, 우선 얼핏 보기에는 SystemMessage 객체와 UserMessage 객체를 생성하며 리스트로 반환한다는 것을 알 수 있습니다. 이는 LLM 모델에게 대화 메시지를 구성 및 전달하기 위해 사용되는 객체 리스트입니다.

컨트롤러 코드는 여기에서 확인하실 수 있습니다.

List<Message> messages = Arrays.asList(
        new SystemMessage(systemMessage),
        new UserMessage(userInput)
);

 

우선 아래의 코드를 먼저 살펴볼텐데 LLM에게 "지시"하기 위한 메시지가 들어갑니다. 컨트롤러에서 systemMessage 변수에 "You are a helpful AI assistant." 라는 문자열을 입력한 상태이며 LLM에게 이러한 지시를 하기 위한 용도입니다.

즉 지시를 내린다는 것은 모델에게 역할, 대화의 톤 등을 지정할 수 있습니다. (ex. "너는 콜센터 직원이야" 등)

new SystemMessage(systemMessage)

아래의 코드를 직접 확인해보면 어떠한 기능이 숨겨져 있다기 보다는 입력받은 값들이 필드에 저장되는 역할을 수행합니다.

SystemMessage(String) 생성자
SystemMessage의 부모 클래스(AbstractMessage) 생성자

 

UserMessage의 경우 사용자가 어떠한 질문, 요청을 했는지를 담게 됩니다. 현재 구현된 코드의 경우 사용자가 입력한 요청은 query 파라미터에 담겨 오게 되는데 이 내용이 userInput으로 들어간다고 보시면 됩니다. (ex. "갑자기 결제 요청 승인 문자가 왔어요")

마찬가지로 입력받은 값을 필드에 저장하는 용도로 쓰입니다.

new UserMessage(userInput)

 


 

2. 챗 옵션 구성 파악하기

챗 옵션을 구성하는 코드는 아래와 같습니다. 빌더를 사용하여 옵션을 지정하는 객체로써 model의 경우 사용자가 요청한 모델(ex. gpt-3.5-turbo)로 지정되며 temperature의 경우 double 타입의 실수를 0부터 1 사이의 값을 설정할 수 있습니다.

OpenAiChatOptions chatOptions = OpenAiChatOptions.builder()
        .model(model)
        .temperature(0.7d)
        .build();

 

이 옵션의 경우 모델이 답변을 생성할 때 얼마나 창의적이고 무작위로 말할지 결정할 수 있습니다. 즉, 특정 질문에 대해 항상 같은 답변을 할지 아니면 다양하고 창의적인 답변을 할지 정할 수 있습니다.

낮을수록 정확하고 예측 가능한 답변을 주며 이로 인해 항상 비슷한 답변을 받을 수 있습니다. 보통 공식 문서에 대한 요약, 정보를 정리하는 것에 적합합니다.

예시) temperature = 0.2d
Q. 사과에 대해 설명해줘
A. 사과는 장미과에 속하는 과일로, 일반적으로 붉은색 또는 녹색을 띕니다. 식용으로 사용되며 비타민C, 식이섬유 등을 함유하고 있습니다.

반대로 높을수록 창의적이고 유연한 답변을 주며 이로 인해 같은 질문도 다양한 방식으로 답변합니다. 때문에 마케팅, 글쓰기 등에 적합합니다.

예시) temperature = 0.8d
Q. 사과에 대해 설명해줘
A. 사과는 새콤달콤한 맛이 나는 과일이에요. 붉은색이나 녹색 껍질을 가지고 있고, 간식이나 주스로 많이 먹죠.

 


 

3. 프롬프트 생성 파악하기

여태 만들어두었던 메시지와 챗 옵션을 인자로 넘겨주어 Prompt 객체를 생성합니다. 만들어 두었던 정보들을 가지고 Prompt 객체 필드에 저장합니다.

Prompt prompt = new Prompt(messages, chatOptions);

 


 

4. 챗 모델 생성 및 호출 파악하기

빌더를 사용하여 openAiApi 객체를 주입하여 OpenAiChatModel 객체를 생성합니다.

OpenAiChatModel chatModel = OpenAiChatModel.builder()
        .openAiApi(openAiApi)
        .build();

return chatModel.call(prompt);

 

이후 chatModel 객체의 call() 메서드를 호출하여 prompt를 인자로 넘겨주게 되는데, prompt 객체에는 여태 구성했던 정보(메시지, 챗 옵션)가 들어있으므로 이제부터 ai를 호출합니다. LLM에게 질문을 보내고 응답을 받아오는 메서드로써 요청 후 응답은 return 됩니다.

call() 메서드를 확인하면 아래와 같은데, 본격적인 api 호출은 internalCall() 메서드이며 본격적으로 요청하기 전에 buildRequestPrompt(Prompt)를 호출하여 최종적으로 요청 프롬프트를 구성합니다.

chatModel.call(Prompt)

이후 buildRequestPrompt(prompt) 및 internalCall(requestPrompt, null)은 각각 이후 포스팅으로 작성됩니다.

반응형