Spring & Spring Boot

[Spring&SpringBoot] ChapGPT Stream으로 응답받기

김먼저 2023. 5. 31. 16:06

 

준비물 : OpenAI Secret API Key

 

https://firstws.tistory.com/63

 

[OpenAI] OpenAI API Key 발급 받기(feat.chatGPT)

0. OpenAI API Key OpenAI에서 개발한 인공지능 모델들을 외부에서 사용할 수 있도록 인증하는 역할을 합니다. OpenAI는 다양한 유형의 인공지능 모델을 제공하는데요, 대표적인 모델을 살펴보면 아래와

firstws.tistory.com

 

 

0. Postman으로 Test 해보기

 

method : POST
url : https://api.openai.com/v1/chat/completions

 

Headers Setting

 

Content-Type : application/json
Authorization : ${OpenAI Secret API Key}

 

 

 

Body-JSON

 

{
  "model": "gpt-3.5-turbo",
  "stream" : true,
  "messages": [{"role": "user", "content": "Springboot의 장점이 뭐야?"}]
}

 

 

'Send' 클릭해서 요청보내기 - 결과

 

 

위와 같이 응답이 여러개 온다면 성공한 겁니다.

 

실제 ChatGPT를 사용할 때 답변을 한번에 주는게 아니라 마치 타이핑 치는 것 처럼 출력됩니다. 그 기능을 활성화

 

시키려면 Stream Parameter에 True값을 셋팅해 요청하면 됩니다. (False일 때는 완성된 답변을 반환합니다.)

 

Stream은 SSE(Server-Sent-Events) 즉, 서버와 한번 연결을 맺고나면 일정 시간동안 서버에서 변경이 발생할 때마다

 

데이터를 전송받는 방식을 사용합니다.

 

ref : https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/

 

Spring에서 Server-Sent-Events 구현하기

tecoble.techcourse.co.kr

 

 

설정 - DTO - Config - Controller - Service 순서로 작성해보겠습니다.

 

 

1. 설정 

 

spring-boot-starter-webflux의 SSE를 사용하여 stream으로 받아올겁니다.

 

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
</dependencies>

 

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

 

 

 

2. DTO

 

@Getter
@NoArgsConstructor
//Front단에서 요청하는 DTO
public class QuestionRequest implements Serializable {
    private String question;
}
@Getter
@NoArgsConstructor
//chatGPT에 요청할 DTO Format
public class ChatGptRequest implements Serializable {
    private String model;
    @JsonProperty("max_tokens")
    private Integer maxTokens;
    private Double temperature;
    private Boolean stream;
    private List<ChatGptMessage> messages;

    //@JsonProperty("top_p")
    //private Double topP;

    @Builder
    public ChatGptRequest(String model, Integer maxTokens, Double temperature,
                          Boolean stream, List<ChatGptMessage> messages
                          /*,Double topP*/) {
        this.model = model;
        this.maxTokens = maxTokens;
        this.temperature = temperature;
        this.stream = stream;
        this.messages = messages;
        //this.topP = topP;
    }
}

 

 

 

3. Config 작성하기

@Configuration
public class ChatGptConfig {
    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";
    public static final String CHAT_MODEL = "gpt-3.5-turbo";
    public static final Integer MAX_TOKEN = 300;
    public static final Boolean STREAM = true;
    public static final String ROLE = "user";
    public static final Double TEMPERATURE = 0.6;
    //public static final Double TOP_P = 1.0;
    public static final String MEDIA_TYPE = "application/json; charset=UTF-8";
    public static final String CHAT_URL = "https://api.openai.com/v1/chat/completions";
}

 

 

 

4. Controller 작성하기

@PostMapping(value="ask-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> ask(Locale locale,
                            HttpServletRequest request,
                            HttpServletResponse response,
                            @RequestBody QuestionRequest questionRequest){
        try {
            return chatGptService.ask(questionRequest);
        }catch (JsonProcessingException je){
            log.error(je.getMessage());
            return Flux.empty();
        }
    }

 

 

5. Service 작성하기

private final ObjectMapper objectMapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE );
    public Flux<String> ask(QuestionRequest questionRequest) throws JsonProcessingException {

        client = WebClient.builder()
                .baseUrl(ChatGptConfig.CHAT_URL)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(ChatGptConfig.AUTHORIZATION,ChatGptConfig.BEARER + apiKey)
                .build();

        List<ChatGptMessage> messages = new ArrayList<>();
        messages.add(ChatGptMessage.builder()
                .role(ChatGptConfig.ROLE)
                .content(questionRequest.getQuestion())
                .build());
        ChatGptRequest chatGptRequest = new ChatGptRequest(
                ChatGptConfig.CHAT_MODEL,
                ChatGptConfig.MAX_TOKEN,
                ChatGptConfig.TEMPERATURE,
                ChatGptConfig.STREAM,
                messages
                //ChatGptConfig.TOP_P
        );
        String requestValue = objectMapper.writeValueAsString(chatGptRequest);

        Flux<String> eventStream = client.post()
                .bodyValue(requestValue)
                .accept(MediaType.TEXT_EVENT_STREAM)
                .retrieve()
                .bodyToFlux(String.class);
        return eventStream;
    }

 

 

실행 결과

 

 

 

테스트상 최소한의 parameter를 사용하였으며, 적용 가능한 parameter가 다양하므로 아래 공식문서를 참조해주세요.

 

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

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com