https://platform.openai.com/api-keys
위 링크에 들어가 키를 발급받는다
이 때 팝업 창이 뜨면서 키 값이 발급되는데, 바로 복사를 해 두어야 한다. 창을 닫으면 더 이상 조회할 수 없고, 이 때는 키를 다시 재발급 받아야 하기 때문이다.
https://api.openai.com/v1/chat/completions
Key값에 Autorization, Value값에 Bearer + 발급받은 key값을 넣어주면 된다.
json 형식으로 넣어주면 된다. 구조는 다음과 같다.
{
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant that categorizes text into three categories: Reflection ('반성문'), Frugality Confirmation ('절약 인증'), and Neither ('판단 안됨'). Based on the given text, you will return a response in the format [category, content]. The content should be a message you would give to a friend: scolding for '반성문' or praise for '절약 인증'. Use emojis to make it feel like a friendly conversation, and keep the message under 200 characters. Please write it without using a comma in the content. Don't use honorifics. Pretend you're talking informally to your friend. Focus on your current spending. Don't argue about cost-effectiveness or efficiency."
},
{
"role": "user",
"content": "길가다가 귀여운 부적이 보이길래 샀어."
},
{
"role": "assistant",
"content": "[반성문, 돈 쓰는 게 취미니? 💸 너무 쓰지 말고, 저금도 좀 해! 은행에 쌓아놓은 돈으로 ‘노후 준비'라는 거 알아? 나중에 맛있는 거 먹으려면 지금 좀 아껴야 해! 😜]"
}
],
"temperature": 0.7,
"max_tokens": 200
}
model : 사용하고 싶은 gpt 모델. 가장 최신 버전을 사용하려면 gpt-4o를 적어주면 됨
messages : 대화의 흐름을 나타내는 메세지 리스트
role : 메세지를 보내는 주체
temperature : 응답의 창의성을 조정하는 매개변수. 최대값이 1이며, 1에 가까울수록(값이 높을수록) 응답이 더 창의적이고, 0에 가까울수록(낮을수록) 일관됨. 위의 예시에서는 창의적인 답변을 원하기 때문에 0.7로 설정했고, 질문의 답변과 가장 가까운 답을 얻고 싶으면 0으로 해주면 됨.
max_tokens : 생성할 수 있는 최대 토큰 수. 200으로 설정해두면 최대 200개의 토큰(단어)가 생성될 수 있는 것. 단어의 수라고 생각하면 편하지만, 한 단어가 두 개의 토큰으로 나누어 질 수 있으므로 토큰의 단위가 더 작다고 생각하면 됨(ex)chatting : chat + ting)
아래와 같은 형식으로 응답이 온다. 서버에서 choices > message > content를 추출해서 사용하면 된다.
openai.model=gpt-4.0
openai.api.key={OpenAI_Key}
openai.api.url=//api.openai.com/v1/chat/completions
OpenAI API와 통신하기 위한 설정 클래스
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;
//OpenAI API와 통신하기 위한 메소드
@Configuration
public class OpenAIConfig {
@Value("${openai.api.key}") //application.properties에서 값을 읽어와 필드에 주입
private String openAIKey;
@Bean(name = "openAIRestTemplate") //RestTemplate를 생성하고 반환. Http 요청을 간편하게 처리할 수 있는 유틸리티 클래스
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(((request, body, execution) -> { //인터셉터 추가
//인터셉터 : Http 요청을 가로채 특정 작업을 수행할 수 있게 해 줌. 사용자의 요청이 컨트롤러에 가기 전에 가로채고, 서버의 응답이 사용자에게 가기 전에 가로챔
//request : Http 요청(요청 메소드, url, 헤더 등과 같은 정보)
//body : 요청의 본문. 필요하지 않다면 null(null일 경우에는 사용할 필요 X)
//execution : 실제 요청을 실행하는 메소드 제공. 인터셉터에서 요청을 가로채고 수정한 후, execution.execute를 호출해 수정된 요청을 서버로 전달하고 응답을 받을 수 있음
HttpHeaders headers = request.getHeaders(); //헤더 추가
String apiKey = "Bearer " + openAIKey;
headers.add("Authorization", apiKey); //헤더에 key값 설정
headers.add("Content-Type", "application/json"); //데이터의 형식을 json으로 설정
return execution.execute(request, body); //실제로 서버에 Http 요청을 보내는 작업 수행.
//위의 return문은 인터셉터에서 수정된 요청이 서버로 전달된 후의 응답을 받아서 호출자에게 반환하는 역할을 함
}));
return restTemplate; //객체 반환. RestTemplate을 사용하여 Http 요청을 보낼 때마다 해당 인터셉터가 자동으로 요청을 가로채고 필요한 헤더를 추가한 뒤 요청 전송
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIRequestDTO {
private String model;
private List<Message> messages;
private double temperature;
private int max_tokens;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Message {
private String role;
private String content;
}
}
마찬가지로 DTO에 위의 빨간 글씨를 전부 넣어 주어야 한다.
나는 여기에서 content값만 필요하기 때문에 응답값에서 content값만 추출해서 사용해주었다.
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIResponseDTO {
private String id;
private String object;
private long created;
private String model;
private List<Choice> choices;
private Usage usage;
private String system_fingerprint;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Choice {
private int index;
private Message message;
private Object logprobs;
private String finish_reason;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Message {
private String role;
private String content;
private Object refusal;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Usage {
private int prompt_tokens;
private int completion_tokens;
private int total_tokens;
}
}
import com.choikang.poor.the_poor_back.dto.OpenAIRequestDTO;
import com.choikang.poor.the_poor_back.dto.OpenAIResponseDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
@Service
public class OpenAIService {
private final RestTemplate restTemplate;
@Autowired
public OpenAIService(@Qualifier("openAIRestTemplate") RestTemplate restTemplate) { //특정 타입의 빈을 주입받기 위해 Qualifier 사용
this.restTemplate = restTemplate; //클래스 내의 필드에 저장해 필요할 때마다 꺼내서 사용할 수 있도록 함
}
//응답값에서 메세지만 추출
public String[] getResponseMessage(OpenAIRequestDTO requestDTO) {
//Http 요청을 보낼 때 사용되는 HttpEntity 객체 생성
//requestDTO 객체는 Http 요청 본문에 들어갈 데이터를 가지고 있음
HttpEntity<OpenAIRequestDTO> requestEntity = new HttpEntity<>(requestDTO);
// API 호출
String openAIUrl = "https://api.openai.com/v1/chat/completions"; //API를 호출할 url
try {
ResponseEntity<OpenAIResponseDTO> responseEntity = restTemplate.exchange(
openAIUrl, //요청을 보낼 url
HttpMethod.POST, //사용할 Http 메서드
requestEntity, //요청 본문과 헤더를 포함하는 객체. 요청 본문이 필요 없는 경우에는 HttpEntity<Void> 사용
OpenAIResponseDTO.class //응답 본문을 DTO 클래스의 인스턴스로 변환
);
OpenAIResponseDTO response = responseEntity.getBody(); //exchange 메소드에서 반환된 객체의 body를 반환
if (response != null && response.getChoices() != null && !response.getChoices().isEmpty()) {
String responseStr = response.getChoices().get(0).getMessage().getContent();
return responseStr.split(",", 2);
}
return new String[]{"판단 안됨", null};
} catch (HttpClientErrorException e) {
System.err.println("응답 바디: " + e.getResponseBodyAsString());
throw e;
}
}
}
여기서 @Autowired을 활용하여 의존성을 주입받는다. 이 때, 의존성을 주입받는 것을 리모컨에 비유할 수 있다.
만약, 집에 있는 리모컨을 가지고 TV를 켜고 싶은 상황을 가정해보자. 이 때, 리모컨을 RestTemplate라고 생각하자. 누군가가 리모컨을 건네주고, 이것을 내가 손에 들고 있으면 언제든지 TV를 켜거나 채널을 바꿀 수 있는 것이다.
@Autowired를 통해 의존성을 주입하는 것은 리모컨을 건네받는 행동이라고 생각하면 된다. 이제 의존성 주입을 통해 리모컨을 건네받았으므로 필요할 때마다 사용할 수 있다. 필요 시마다 사용하기 위해 RestTemplate을 클래스 내의 필드에 저장해 두는 것이다.
더미데이터를 넣고 gpt의 응답이 제대로 오는지 확인하기 위한 test code
package com.choikang.poor.the_poor_back.OpenAITest;
import com.choikang.poor.the_poor_back.dto.OpenAIRequestDTO;
import com.choikang.poor.the_poor_back.service.OpenAIService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class OpenAIServiceTest {
@Autowired
private OpenAIService openAIService;
@Test
void testOpenAIRequest() {
// 더미데이터 넣기
OpenAIRequestDTO requestDTO = new OpenAIRequestDTO();
requestDTO.setModel("gpt-4");
requestDTO.setMessages(List.of(
new OpenAIRequestDTO.Message("system",
"You are a helpful assistant that categorizes text into three categories: Reflection ('반성문'), Frugality Confirmation ('절약 인증'), and Neither ('판단 안됨'). Based on the given text, you will return a response in the format category, content. The content should be a message you would give to a friend: scolding for '반성문' or praise for '절약 인증'. Use emojis to make it feel like a friendly conversation, and keep the message under 200 characters. Please write it without using a comma in the content. Don't use honorifics. Pretend you're talking informally to your friend. Focus on your current spending. Don't argue about cost-effectiveness or efficiency."),
new OpenAIRequestDTO.Message("user", "당근마켓에서 100만원주고 노트북 샀어")
));
requestDTO.setTemperature(0.7);
requestDTO.setMax_tokens(200);
//응답 받아와서 추출 및 출력하기
try {
String[] response = openAIService.getResponseMessage(requestDTO);
System.out.println(response);
String gptAnswerType = response[0];
String gptAnswerContent = response[1];
System.out.println("타입: " + gptAnswerType);
System.out.println("내용: " + gptAnswerContent);
} catch (Exception e) {
System.out.println("예외 발생: " + e.getMessage());
e.printStackTrace();
}
}
}