Spring으로 GPT API를 사용해보자(2)

1
post-thumbnail

프로젝트 생성

이 글을 보는 사람은 프로젝트 생성정도는 할 줄 알거라 판단하고
간단하게 글로만 적고 넘기도록함

jdk 17
spring boot 3.2.0

Spring web
Lombok

폴더 구조

properties

properties에 사용할 gpt 모델과 gpt api key
gpt api url을 아래와 같이 설정해준다


gpt.model=gpt-3.5-turbo
gpt.api.key=발급 받은 api키
gpt.api.url=https://api.openai.com/v1/chat/completions

Cofiguration

우선 요청을 보내기 위해 RestTemplate을 설정하는 클래스가 필요하다


package com.example.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class GPTConfig {

    @Value("${gpt.api.key}")
    private String apiKey;
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate template = new RestTemplate();
        template.getInterceptors().add((request, body, execution) -> {
            request.getHeaders().add(
                    "Authorization"
                    ,"Bearer "+apiKey);
            return execution.execute(request,body);
        });

        return template;

    }

}

restTemplate를 @Bean 어노테이션을 사용하여 Bean에 등록 시켜준다

RestTemplate 인스턴스를 생성하고 HTTP 요청의 헤더에 Authorization 헤더를 추가하는 인터셉터를 설정함 이 인터셉터는 이제 모든 요청에 대해 OpenAI API 키를 포함시킴

DTO

GPTRequest

요청을 보낼때 Json 형식이
다음과 같이 필요로하다

{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "user",
      "content": ""
    }
  ],
  "temperature": 1,
  "max_tokens": 256,
  "top_p": 1,
  "frequency_penalty": 0,
  "presence_penalty": 0
}

"temperature": 1,
"max_tokens": 256,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0
는 필수가 아니라 있어도 되고 없어도 되는거 같다


package com.example.demo.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class GPTRequest {

    private String model;
    private List<Message> messages;
    private int temperature;
    private int maxTokens;
    private int topP;
    private int frequencyPenalty;
    private int presencePenalty;

    public GPTRequest(String model
            , String prompt
            , int temperature
            , int maxTokens
            , int topP
            , int frequencyPenalty
            , int presencePenalty) {
        this.model = model;
        this.messages = new ArrayList<>();
        this.messages.add(new Message("user",prompt));
        this.temperature = temperature;
        this.maxTokens = maxTokens;
        this.topP=topP;
        this.frequencyPenalty=frequencyPenalty;
        this.presencePenalty = presencePenalty;

    }
}

@JsonProperty 어노테이션을 사용해 필드에 하나씩 다 매핑 해주는 방법도 있지만

@JsonNaming을 사용하면 간단하게 snake case로 바꿔준다 혹시 @JsonNaming을 처음 본다면
아래 주소를 참고

https://velog.io/@gyuha/JsonNaming%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%84%A4%EB%AA%85

생성자는 List를 구현하는 ArrayList로 한 다음
Message을 생성할때 roler과 prompt를 추가해준다.

Meesage 클래스는 DTO 마지막에 있다

참고

아까도 말했지만

"temperature": 1,
"max_tokens": 256,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0

얘네는 필수가 아니다
model,messages만 있어도 동작함

GPTResponse

요청을 보냈으니 응답을 받을 클레스도 필요하다

아래와 같이 만들어주면 된다.


package com.example.demo.dto;

import lombok.*;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GPTResponse {
    
    private List<Choice> choices;


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Choice {
        //gpt 대화 인덱스 번호
        private int index;
        // 지피티로 부터 받은 메세지
        // 여기서 content는 유저의 prompt가 아닌 gpt로부터 받은 response
        private Message message;

    }
}

내부 클레스로 Choice를 만들어서
인덱스 번호와 message를 뽑아오면 된다

Mesaage

이제 응답과 요청을 주고 받을떄

필요한 Message 클레스를 만들면 된다.

package com.example.demo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {

    private String role;
    private String content; 

}

여기서 content는 요청을 보낼땐 prompt가 되고 받을땐 gpt가 응답한 내용이 된다

Controller

이제 api tester로 테스트 하기위한 컨트롤러를 만들어준다.

package com.example.demo.controller;


import com.example.demo.dto.GPTRequest;
import com.example.demo.dto.GPTResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/gpt")
@RequiredArgsConstructor
public class GPTController {

    @Value("${gpt.model}")
    private String model;

    @Value("${gpt.api.url}")
    private String apiUrl;
    private final RestTemplate restTemplate;


    @GetMapping("/chat")
    public String chat(@RequestParam("prompt") String prompt){

        GPTRequest request = new GPTRequest(
                model,prompt,1,256,1,2,2);

        GPTResponse gptResponse = restTemplate.postForObject(
                apiUrl
                , request
                , GPTResponse.class
        );


        return gptResponse.getChoices().get(0).getMessage().getContent();


    }
}

Message 클래스에 content는 보낼땐 prompt가 되고
받을땐 gpt로부터 받은 응답내용이 된다.

 "temperature": 1,
  "max_tokens": 256,
  "top_p": 1,
  "frequency_penalty": 0,
  "presence_penalty": 0

이 부분을 필드에 지정 하지 않았다면 GPTRequest 생성자 매개변수에
prompt 이후 부분은 다 필요없다

결과

성공적으로 됐지만 개인적으로 0으로 맞추는거 추천
frequency penalty,presence penalty를 높였더니 애가 이상해짐

마치며

이상한 부분이나 잘못된 부분이 있다면 지적 부탁드립니당

2개의 댓글

comment-user-thumbnail
2025년 3월 31일

테스트 어떤걸로 하신거에요 ? (마지막 사진)

1개의 답글