티스토리 뷰

 

이전 글에서 https://api.openai.com/v1/chat/completions API에 대해 간단히 다뤄보았다.

 

그런데 앞선 글이 너무 겉핥기라 정작 중요한 부분을 몇 가지를 놓친 것 같아서 추가적으로 정리해보려고 한다.

 

크게 두 가지 부분이 아쉬워서 내용을 보강해보려고 한다.

 

첫 번째는 GPT 프롬프트(Prompt)라고 알려진 메시지 프롬프트가 들어가는 message 배열 부분

 

두 번째는 현재 GPT가 답변을 주는 것처럼 한 글자씩 내려주는 stream 부분이다.

 

하나씩 알아보자.

 

1. message 배열

먼저, user는 일반적인 사용자를 의미한다.

 

우리가 평소에 GPT를 사용할 때처럼 아래와 같이 작성하고 보내도, GPT는 정상적으로 답변을 보내준다.

"messages": [
  {
    "role" : "user",
    "content": "저녁 메뉴를 추천해줘"
  }
]

//답변 : 1. 스테이크와 샐러드\n2. 연어 초밥\n3. 파스타\n4. 치킨 테리야끼와 쌀밥\n5. 새우튀김과 라면

 

위처럼 다양한 답변을 준다. 

 

하지만, 여기서 system에 추가 정보를 넣는다면, 답변이 달라진다.

"messages": [
  {
    "role" : "system" 
    "content" : "You are a chef specializing in Korean food."
  },
  {
    "role" : "user",
    "content" : "저녁 메뉴를 추천해줘"
  }
]
// 답변 : 불고기, 김치찌개, 된장찌개, 비빔밥, 불닭, 잡채, 떡볶이, 김밥, 해물파전, 삼겹살, 갈비찜, 냉면, 순두부찌개, 김치볶음밥

시스템에게 한식 요리사라는 조건을 넣었다. 답변은 한식만 나온다.

 

이렇게 GPT가 답변을 어떤 식으로 해야할 지 미리 유도할 수 있도록 만드는 기술이 GPT 프롬프트이다.

 

2. stream 

다음은 stream 옵션인데, 이 옵션을 사용하지 않으면 이전 API(completion)과 동일하게 답변을 한번에 만들어 전달한다.

 

하지만 GPT chat 페이지를 가보면 답변을 생성할 때, 한글자씩 찍어내면서 답변을 준다.

 

 

여기서 SSE(Sever Sent Event)라는 기술이 사용되는데, stream이 이 옵션을 켜고 끄는 기능이다.

 

openAI에서 응답을 SSE로 주기 때문에 프론트에서 제대로 표현하려면 내 서버도 SSE 형태로 응답을 주어야한다.

 

다행히 스프링 프레임워크에서도 SSE를 지원한다.

 

SseEmitter 를 쓰면 되는데, OpenAI와 연동해서 쓰려면 헤더에 Key를 설정해야해서 WebClient와 연동해서 사용했다.

 

public SseEmitter getChatCompletionStream(List<ChatMessage> messages) {
    ChatRequestDto chatRequestDto = ChatRequestDto.builder().model(CHAT_GPT_MODEL).messages(messages).maxTokens(maxTokens).temperature(temperature).stream(true).build();

    SseEmitter emitter = new SseEmitter((long) (5 * 60 * 1000));
    WebClient client = WebClient.create("https://api.openai.com/v1");


    client.post().uri("/chat/completions")
          .header("Content-Type", "application/json")
          .header("Authorization", openAIAPIKey)
          .body(BodyInserters.fromValue(chatRequestDto))
          .exchange()
          .flatMapMany(response -> response.bodyToFlux(String.class))
          .doOnNext(line -> {
              try {
                  emitter.send(SseEmitter.event().data(line));
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          })
          .doOnError(emitter::completeWithError)
          .doOnComplete(emitter::complete)
          .subscribe();
    return emitter;
}

마지막에 [Done]이 뜨면 마무리가 된 것이다.

 

주의할 점은 생성자에 꼭 Timeout 시간을 특정시간 이상으로 설정해줘야한다는 것이다.

 

디폴트는 30초인데, 문장이 길어진다면 30초는 그냥 넘게 써서 타임아웃이 자주 발생한다.

 

2023.06.04 수정

 

위 코드는 SseEmitter가 종료되지 않기 때문에 서비스 어플리케이션에서는 사용할 수 없는 코드다.

 

또 WebClient의 exchange는 메모리 릭 이슈로 depreacated 된 코드다.

 

때문에 아래와 같이 수정했다.

public SseEmitter getChatCompletionStream(List<ChatMessage> messages) {
    ChatRequestDto chatRequestDto = ChatRequestDto.builder().model(CHAT_GPT_MODEL).messages(messages).maxTokens(maxTokens).temperature(temperature).stream(true).build();

    SseEmitter emitter = new SseEmitter((long) (5 * 60 * 1000));
    WebClient client = WebClient.create("https://api.openai.com/v1");


    client.post().uri("/chat/completions")
          .header("Content-Type", "application/json")
          .header("Authorization", openAIAPIKey)
          .body(BodyInserters.fromValue(chatRequestDto))
          .exchangeToFlux(response -> response.bodyToFlux(String.class))
          .doOnNext(line -> {
              try {
                    if (line.equals("[DONE]")) {
                        emitter.complete();
                        return;
                    }
                  emitter.send(SseEmitter.event().data(line));
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          })
          .doOnError(emitter::completeWithError)
          .doOnComplete(emitter::complete)
          .subscribe();
    return emitter;
}

 

마치며

위 프롬프트와 stream 두 기능을 이용한다면, 좀 더 chat 사이트처럼 사용이 가능하다.

 

GPT를 이용해서 무언가를 만든다고 한다면, 이 옵션의 사용법들을 익혀두면 좋을 것이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함