티스토리 뷰
이전 글에서 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를 이용해서 무언가를 만든다고 한다면, 이 옵션의 사용법들을 익혀두면 좋을 것이다.
'개발 > chatGPT' 카테고리의 다른 글
Chat GPT API에서 이전 대화 기억하게 하기 (9) | 2023.06.25 |
---|---|
Chat GPT의 입력 Token 수를 세보자(Jtokkit 소개) (0) | 2023.05.24 |
Chat GPT API에서 사용하는 SSE(Server-Sent Events) 뜯어보기 (2) | 2023.05.14 |
ChatGPT API의 새기능? chat API를 써보자 - 1 (간단 사용법) (0) | 2023.04.24 |
ChatGPT API를 스프링 프레임워크에서 써보기 (0) | 2023.02.05 |
- Total
- Today
- Yesterday
- elasticsearch
- AWS
- Spring
- 오블완
- 후쿠오카
- springboot
- AOP
- CloudFront
- java
- OpenAI
- lambda
- openAI API
- Elastic cloud
- 스프링부트
- GIT
- AWS EC2
- serverless
- OpenFeign
- terraform
- cache
- EKS
- 티스토리챌린지
- Log
- Kotlin
- docker
- ChatGPT
- MySQL
- S3
- 람다
- JWT
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |