ChatGPT를 사용하여 이미지 분석을 요청하는 서비스를 개발하려고 한다.
- Spring boot ChatGPT API를 호출하는 방법
- base64로 인코딩된 문자열 데이터를 GPT에게 분석 요청
- Dto 구조 설계
GPT-4 및 GPT-3.5와 같은 OpenAI의 텍스트 생성 모델은 자연어와 형식 언어를 이해하도록 훈련. GPT-4와 같은 모델은 입력에 대한 응답으로 텍스트 출력을 허용. 프롬프트 디자인은 본질적으로 GPT-4와 같은 모델을 "프로그래밍"하는 방법으로, 일반적으로 작업을 성공적으로 완료하는 방법에 대한 지침이나 몇 가지 예를 제공. GPT-4와 같은 모델은 콘텐츠 또는 코드 생성, 요약, 대화, 창의적 글쓰기 등을 포함한 매우 다양한 작업에 사용.
모델 | 개념 |
---|---|
GPT-3 | 현재까지 가장 큰 규모의 GPT 모델. 매우 다양한 언어 작업을 수행할 수 있으며, 명시적인 학습 없이도 특정 작업을 수행할 수 있는 능력 보유. |
GPT-4(Vision) | GPT-3의 후속 모델. 텍스트 예측과 RLHF로 훈련됨. 텍스트와 이미지를 입력으로 받음. |
DALL·E | 자연어 프롬프트를 기반으로 이미지를 생성하고 편집할 수 있는 모델 |
Whisper | 오디오를 텍스트로 변환할 수 있는 모델 |
사용할 모델은 GPT-4(Vision) - 유료
OpenAI Api에서 제공해 주는 여러가지 api 기능은 공식 문서를 참고
공식 문서
내가 사용할 기능은 입력 이미지가 주어지면 모델은 이미지를 분석하고 새로운 텍스트를 생성
분류 | 상세 분류 | HTTP Method | Endpoint | 설명 |
---|---|---|---|---|
Chat (채팅) | Create chat completion | POST | https://api.openai.com/v1/chat/completions | 지정된 채팅 대화에 대한 모델 응답을 생성 |
OpenAI API Key 발급
- OpenAI API Key를 헤더에 포함시켜 데이터를 전송한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
openai.secret-key= ****
openai.url.prompt=https://api.openai.com/v1/chat/completions
ContentDto
package gptdto.gptserviceV4.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
@JsonInclude(JsonInclude.Include.NON_NULL) // null 값이 아닌 필드만 JSON에 포함
public class ContentDto {
private String type;
private String text;
@JsonProperty("image_url") // JSON 필드명을 "image_url"로 지정
private ImageURLDto imageUrl; // 필드명을 카멜케이스로 변경
// 텍스트 콘텐츠 전용 생성자
public ContentDto(String type, String text) {
this.type = type;
this.text = text;
}
// 이미지 URL 콘텐츠 전용 생성자
public ContentDto(String type, ImageURLDto imageUrl) {
this.type = type;
this.imageUrl = imageUrl;
}
}
ImageAnalysisRequestDto
package gptdto.gptserviceV4.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter @Setter
public class ImageAnalysisRequestDto {
private String model;
private List<MessageDto> messages;
public ImageAnalysisRequestDto(String model, List<MessageDto> messages) {
this.model = model;
this.messages = messages;
}
}
ImageURLDto
package gptdto.gptserviceV4.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Lob;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ImageURLDto {
@Lob @JsonProperty("url")
private String url;
}
MessageDto
package gptdto.gptserviceV4.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter @Setter
public class MessageDto {
private String role;
private List<ContentDto> content;
public MessageDto(String role, List<ContentDto> content) {
this.role = role;
this.content = content;
}
}
package gptdto.gptserviceV4.config;
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.http.MediaType;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ChatGPTConfig {
@Value("${openai.secret-key}")
private String secretKey;
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public HttpHeaders httpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(secretKey);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
}
package gptdto.gptserviceV4.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import gptdto.gptserviceV4.config.ChatGPTConfig;
import gptdto.gptserviceV4.dto.ContentDto;
import gptdto.gptserviceV4.dto.ImageAnalysisRequestDto;
import gptdto.gptserviceV4.dto.ImageURLDto;
import gptdto.gptserviceV4.dto.MessageDto;
import gptdto.gptserviceV4.service.ChatGPTService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@RequiredArgsConstructor
@Slf4j
public class ChatGPTServiceImpl implements ChatGPTService {
@Value("${openai.url.prompt}")
private String promptUrl;
private final ChatGPTConfig chatGPTConfig;
String question = "Please organize this receipt in Json format with the key value of the list name and the price as the value value.";
String question1= "I want to analyze my spending history and know where I spent my money. Analysis tells you where, how, and how much you spent, in terms of price.";
@Override
public Map<String, Object> promptV2(ImageURLDto imageUrlDto) {
Map<String, Object> resultMap = new HashMap<>();
HttpHeaders headers = chatGPTConfig.httpHeaders();
ContentDto imageContent = new ContentDto("image_url", imageUrlDto);
ContentDto textContent = new ContentDto("text", question1);
MessageDto message = new MessageDto("user", Arrays.asList(textContent, imageContent));
ImageAnalysisRequestDto requestDto = new ImageAnalysisRequestDto("gpt-4-vision-preview", Collections.singletonList(message));
// 요청 엔티티 생성
return getStringObjectMap(resultMap, headers, requestDto);
}
private Map<String, Object> getStringObjectMap(Map<String, Object> resultMap, HttpHeaders headers, ImageAnalysisRequestDto requestDto) {
HttpEntity<ImageAnalysisRequestDto> requestEntity = new HttpEntity<>(requestDto, headers);
ResponseEntity<String> response = chatGPTConfig.restTemplate()
.exchange(promptUrl, HttpMethod.POST, requestEntity, String.class);
try {
// GPT API 로부터 받은 JSON 형식의 응답 문자 파싱
ObjectMapper om = new ObjectMapper();
resultMap = om.readValue(response.getBody(), new TypeReference<>() {
});
} catch (JsonProcessingException e) {
log.debug("JsonMappingException :: " + e.getMessage());
} catch (RuntimeException e) {
log.debug("RuntimeException :: " + e.getMessage());
}
return resultMap;
}
@Override
public Map<String, Object> prompt(ImageAnalysisRequestDto imageAnalysisRequestDt) {
Map<String, Object> resultMap = new HashMap<>();
HttpHeaders headers = chatGPTConfig.httpHeaders();
// 요청 엔티티 생성
return getStringObjectMap(resultMap, headers, imageAnalysisRequestDt);
}
}
package gptdto.gptserviceV4.controller;
import gptdto.gptserviceV4.dto.ImageAnalysisRequestDto;
import gptdto.gptserviceV4.dto.ImageURLDto;
import gptdto.gptserviceV4.service.ChatGPTService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping(value = "/api/v1/chatGpt")
@RequiredArgsConstructor
public class ChatGPTController {
private final ChatGPTService chatGPTService;
@PostMapping("/prompt")
public ResponseEntity<Map<String, Object>> selectPrompt(@RequestBody ImageAnalysisRequestDto imageAnalysisRequestDto){
Map<String, Object> result = chatGPTService.prompt(imageAnalysisRequestDto);
return new ResponseEntity<>(result, HttpStatus.OK);
}
@PostMapping("/imagePrompt")
public ResponseEntity<Map<String, Object>> imagePrompt(@RequestBody ImageURLDto imageURLDto){
Map<String, Object> result = chatGPTService.promptV2(imageURLDto);
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
다음 사진과 같이 서버에 post 요청
body에 json 형식으로 "url":"base64 이미지 데이터..." 를 받고
응답 데이터로 사진을 분석한 결과가 content 안에 String 으로 잘 전달이 되었다.