🍃Spring

[Spring] 스프링 부트에서 Chat GPT API 사용해보기

waveofmymind 2023. 5. 30. 19:44

OPEN AI에서 지원하는 Chat GPT의 API를 사용해서 프롬프트에 대해 응답을 받는 경험을 공유하고자 쓰는 글입니다.

준비 사항

저는 아래의 라이브러리를 사용했습니다.

https://github.com/TheoKanning/openai-java

 

GitHub - TheoKanning/openai-java: OpenAI GPT-3 Api Client in Java

OpenAI GPT-3 Api Client in Java. Contribute to TheoKanning/openai-java development by creating an account on GitHub.

github.com

그리고, 프로젝트에서는 gradle을 사용하기 때문에 아래 의존성을 추가해줍니다.

implementation 'com.theokanning.openai-gpt3-java:api:0.12.0'
implementation 'com.theokanning.openai-gpt3-java:service:0.12.0'

챗 GPT 서비스를 이용하는 데에는 OPEN AI에서 제공하는 토큰이 필요한데, 이것을 발급 받는 것은 아래 링크에서 할 수 있습니다.

https://platform.openai.com/

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

Completion vs ChatCompletion

위 라이브러리에서 제공하는 서비스는 크게 두가지가 있는데, 컴플리션은 단순 프롬프트에 대한 응답을 받을 수 있고, 챗 컴플리션의 경우는 Chat GPT 사이트에서 사용하던 것처럼 기존에 대화하던 것을 GPT가 기억하고 대답을 받을 수 있게됩니다.

 

저는 프로젝트에서 사용자가 원하는 조건과 이력서 정보를 바탕으로 프롬프트를 구성해서 GPT에게 답변을 요청하는 것을 목표로 하고 컴플리션을 사용하려고 했지만,

 

GPT 기준 3.5 터보를 사용하려고 할 때, 컴플리션을 지원하지 않았습니다.

그래서 저는 챗 컴플리션을 사용하고자 하며, OPEN AI 사이트 기준 gpt-4 버전도 지원한다고 되어있으나, gpt-4 버전으로 응답을 요청할 시 찾을 수 없는 모델이라는 오류가 발생해 3.5 터보를 사용하고자 합니다.

 

OpenAiService

저희가 사용하는 라이브러리의 핵심 서비스 클래스입니다.

이를 초기화할 때에는 위에서 발급한 Open AI Token과 타임아웃 시간입니다.

위 서비스는 챗 GPT에 의한 대답이 모두 작성되고나서 응답이 오기 때문에 타임아웃을 설정하지 않으면 TIME OUT 이슈가 발생합니다.

 

저는 그래서 GPT와 관련한 상수와 OpenAiService를 빈으로 등록하기 위해서 GptConfig 클래스를 아래와 같이 정의했습니다.

@Configuration
public class GptConfig {

    public final static String MODEL = "gpt-3.5-turbo";
    public final static double TOP_P = 1.0;
    public final static int MAX_TOKEN = 2000;
    public final static double TEMPERATURE = 1.0;
    public final static Duration TIME_OUT = Duration.ofSeconds(300);

    @Value("${openai.token}")
    private String token;

    @Bean
    public OpenAiService openAiService() {
        return new OpenAiService(token, TIME_OUT);
    }
}

 

각 상수를 설명하면 다음과 같습니다.

  • MODEL: 저희가 사용하고자 하는 모델 ID 값입니다.
  • TEMPERATURE, TOP_P: 생성될 다음 단어의 샘플링 방법 등을 지정하지만, 둘 다 변경하는 것은 권하지 않는다고 합니다. 저는 중간값으로 먼저 서비스를 시험하기 위해 둘다 1로 선언했습니다.
  • MAX_TOKEN: 생성될 답변의 길이를 의미합니다.

저는 타임 아웃 이슈를 피하기 위해 넉넉하게 300초까지 타임아웃으로 설정했습니다.

 

OpenAiService 클래스를 살펴보면 아래와 같이 생성자가 되어있습니다.

public class OpenAiService {

    private static final String BASE_URL = "https://api.openai.com/";
    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);
    private static final ObjectMapper mapper = defaultObjectMapper();

    private final OpenAiApi api;
    private final ExecutorService executorService;

    /**
     * Creates a new OpenAiService that wraps OpenAiApi
     *
     * @param token OpenAi token string "sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
     */
    public OpenAiService(final String token) {
        this(token, DEFAULT_TIMEOUT);
    }

    /**
     * Creates a new OpenAiService that wraps OpenAiApi
     *
     * @param token   OpenAi token string "sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
     * @param timeout http read timeout, Duration.ZERO means no timeout
     */
    public OpenAiService(final String token, final Duration timeout) {
        ObjectMapper mapper = defaultObjectMapper();
        OkHttpClient client = defaultClient(token, timeout);
        Retrofit retrofit = defaultRetrofit(client, mapper);

        this.api = retrofit.create(OpenAiApi.class);
        this.executorService = client.dispatcher().executorService();
    }

	// ...
    
}

생성자의 두번째 파라미터로 타임아웃 시간을 넘기지 않을 경우 기본값인 10초로 되어있기 때문에 타임아웃을 직접 정해주는게 좋습니다.

과정

챗 컴플리션의 경우 OPEN AI API 사이트에서 기능을 번역하면, 채팅 기능처럼 이전의 내용을 기억하고 답변을 제공한다고 했습니다.

요청 데이터로는 아래와 같습니다.

{
  "model": "gpt-3.5-turbo",
  "messages": [{"role": "user", "content": "Hello!"}]
}

유저인지, 시스템인지를 정하고, 보낼 메시지를 담아야합니다.

 

저희가 사용하는 라이브러리에서는 아래와 같이 ChatMessage라는 클래스를 사용할 수 있습니다.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {

	/**
	 * Must be either 'system', 'user', or 'assistant'.<br>
	 * You may use {@link ChatMessageRole} enum.
	 */
	String role;
	String content;
}

저는 프롬프트 기능을 챗 컴플리션에 사용할 것이기때문에, 먼저 프롬프트를 작성해주고 role을 SYSTEM, content에는 생성한 프롬프트 내용을 먼저 요청하고, 두번째로 원하는 사용자의 내용을 보내고자 합니다.

 

그래서 아래와 같이 챗 메시지 객체를 생성했습니다.

ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), prompt);
ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), content);
return List.of(systemMessage, userMessage);

먼저 시스템이 프롬프트에 대해서 기억하고, 유저가 원하는 내용을 요청하면, 기억한 프롬프트를 반영한 답변을 제공하도록 한 것입니다.

그래서 위에서 생성한 두 ChatMessage 객체를 리스트로 만듭니다.

 

그리고 이제 챗 컴플리션을 요청하기 위해 ChatCompletionRequest 객체를 생성하고, OpenAiService가 지원하는 createChatCompletion() 메서드를 사용해서 답변을 제공받아봅시다.

 

우선 GptConfig에서 정의했던 상수와 위에서 만들었던 ChatMessage 배열을 사용해서 ChatCompletionRequest 객체를 생성할 수 있습니다.

public ChatCompletionResult generate(List<ChatMessage> chatMessages) {

        ChatCompletionRequest build = ChatCompletionRequest.builder()
                .messages(chatMessages)
                .maxTokens(GptConfig.MAX_TOKEN)
                .temperature(GptConfig.TEMPERATURE)
                .topP(GptConfig.TOP_P)
                .model(GptConfig.MODEL)
                .build();

        return openAiService.createChatCompletion(build);

    }

createChatCompletion() 메서드를 들어가보면 아래와 같이 되어 있습니다.

public ChatCompletionResult createChatCompletion(ChatCompletionRequest request) {
        return execute(api.createChatCompletion(request));
    }
    
@POST("/v1/chat/completions")
Single<ChatCompletionResult> createChatCompletion(@Body ChatCompletionRequest request);

위에서 생성한 ChatCompletionRequest 객체로 ChatCompletionResult 객체를 얻어옵니다.

ChatCompletionResult 객체는 OPEN AI API사이트에서 볼 수 있다 시피 아래와 같이 구성되어있습니다.

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "\n\nHello there, how may I assist you today?",
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}

그 중 저희가 필요한 내용인 message 배열의 content를 가져오겠습니다.

저는 프롬프트로 JSON 형식으로 content를 내려달라고 했으니 아마 아래와 같은 형태로 올 것입니다.

"predictionResponse" : [
		{ "type" : "Type of question", "question" : "content", "bestAnswer" : "content" },
        	{ repetition }
]

그리고 objectMapper를 통해 JSON을 객체로 변환시켜줍니다.

String futureResult = chatCompletionResult.getChoices().get(0).getMessage().getContent();

return objectMapper.readValue(futureResult, WhatGeneratedResponse.class);

WhatGenerateResponse는 예상 질문과 답변을 리스트로 가지는 객체입니다.

결과

생성한 WhatGenerateResponse를 result.html로 나타내면 다음과 같이 보여줄 수 있습니다.

만약, CHAT GPT에게 받을 메시지를 더 정교하게 만들고 싶으면, 위에서 ChatMessage 객체를 더 추가해서 답변을 요청하면 될 것 같습니다.

또한, Chat GPT 사이트에서 이용할 때와 같이 실시간으로 답변이 생성되는 스트리밍 API도 지원하고 있습니다. 

저는 질문과 대답만을 받기 위한 서비스를 구현하고 있기 때문에, 그에 대한 내용은 저희가 사용한 라이브러리 사이트에서 도움을 받을 수 있을 것 같습니다.

TO-DO

생성 AI를 적용하는 서비스들도 늘어나고 있기 때문에 백엔드 개발자로써 한번쯤은 사용해보는게 좋을 것이라고 생각해서 시작한 프로젝트입니다.

그러나 특정 외부 라이브러리를 사용하기 때문에 OPEN AI의 공식 문서를 살펴보는 것이 큰 도움이 되었습니다.

 

개선 사항으로는 3.5 버전을 사용하지만 답변을 요청할 때 짧게는 1분에서 길게는 5분 정도의 시간이 소요되는 것 같습니다.

외부 API를 사용하는 것이기 때문에 걸리는 시간은 더이상 줄일 수 없을 것이라고 생각해서,

서비스를 제공할 때 미리 소요 시간을 안내하면서, 제출하고 나서 로딩 화면을 추가로 보여주는 것이 좋을 것 같습니다.

 

 

출처

https://github.com/TheoKanning/openai-java

 

GitHub - TheoKanning/openai-java: OpenAI GPT-3 Api Client in Java

OpenAI GPT-3 Api Client in Java. Contribute to TheoKanning/openai-java development by creating an account on GitHub.

github.com

https://platform.openai.com/docs/api-reference/chat/create

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

https://joecp17.tistory.com/72?category=1034519 

 

[Spring Boot + Chat GPT] Open AI API 적용기

서론 최근 Chat GPT가 굉장히 많은 플랫폼에서 상용화가 되고 있고 핫하다는 걸 체감을 많이 느꼈다. Chat GPT를 활용하여 현재 내가 하는 서비스 혹은 이후의 프로젝트들에서 이를 활용해 볼 수 있

joecp17.tistory.com