
이번 글에서는 졸업 프로젝트로 진행 중인 "Neverland: 생성형 AI 기반 단체 추억 아카이빙 앱 서비스"를 개발하는 과정에서 Open AI의 Chat-GPT API를 사용하기 위한 Java 라이브러리 세팅하는 방법과 간단한 요약 기능을 구현하는 과정을 기록해보려 한다.
🧩 1. 프로젝트 소개

본격적으로 구현 과정 소개에 들어가기 전에, 먼저 "Neverland"의 System Architecture와 사용하는 Backend 관련 스택을 소개하고 어떤 기능을 구현하기 위해 Chat-GPT를 사용하는지 정리해보려 한다.
우선 "Neverland"의 System Architecture와 Backend 관련 Stack 리스트는 아래와 같다.

Authorization
• Json Web Token, OAuth2를 활용해 인증/인가 기능을 구현한다.
• 회원가입, 로그인, 로그아웃, 회원탈퇴 기능을 구현한다.
Cloud
• AWS EC2를 활용해 앱 서버를 구축한다.
• AWS S3를 활용해 이미지를 저장한다.
• AWS Route53, ALB를 활용해 도메인 설정 및 HTTPS 프로토콜을 적용한다.
DB
• AWS RDS(MySQL)로 App DB를, Redis로 Token 저장용 DB를 구축한다.
CI/CD
• Docker, Github Actions를 활용해 배포 자동화 시스템을 구축한다.
External API
• Kakao Map API를 활용해 위치 좌표를 저장 및 제공한다.
• Chat-GPT API를 활용해 여러 개의 추억 기록 텍스트를 모아 Stable Diffusion의 input용 프롬프트를 효과적으로 생성한다.
“Neverland”의 주요 기능은 크게 2가지로 구성되어 있다. 첫 번째로, 유저들의 추억 기록을 모아 하나의 공동 추억 기록으로 자동 생성하는 것, 두 번째로는 자동 생성된 이미지를 포함한 추억 기록을 특정 방식(시간, 공간)으로 아카이빙 하는 것이다.
이 중 추억 기록을 모아 하나의 공동 추억 기록으로 자동 생성하는 기능을 구현하기 위해서 Open AI의 Chat-GPT 3.5와 Stability AI의 Stable Diffusion을 활용하는데, 서비스 로직을 좀 더 자세히 살펴보자.
공동 추억 기록 자동 생성 기능의 서비스 로직
공동 추억 이미지를 자동 생성하는 기능을 구현하기 위해, 유저들이 입력한 피드 데이터를 모아 LLM(Chat-GPT)의 프롬프트로 입력한다. 한 추억에 대한 유저들의 여러 데이터들을 Chat-GPT를 통해 하나의 글로 요약한 후, 이미지 생성 AI 모델인 Stable Diffusion의 입력값으로 사용한다. Stable Diffusion 모델은 앞서 요약한 글과 피드 이미지를 이용해, 공동 추억 이미지 데이터를 생성한다. 최종적으로, 생성된 공동 추억 이미지 데이터가 추억 DB에 피드 형식으로 저장되고, 유저의 요청이 들어올 경우 추억 DB를 통해 피드 데이터를 조회할 수 있다.
해당 기능을 구현하기 위한 모듈 구성과 자세한 구현 방식은 다음과 같다.

특정 추억을 함께한 모든 유저의 텍스트를 모아 Chat-GPT를 통해 하나의 텍스트로 요약한다.
이후 유저가 피드 이미지를 업로드했는지 여부에 따라, Stable Diffusion의 입력값과 호출되는 API가 다음 표와 같이 결정된다.

최초 작성자가 이미지를 포함해 글을 작성했다면, 해당 이미지와 Chat-GPT로 요약한 텍스트를 입력값으로 해 Stable Diffusion의 Image-to-Image with prompt API를 호출한다. 그러나 이미지 없이 작성된 피드의 경우, Chat-GPT로 요약한 텍스트만을 입력값으로 해 Text-to-Image API를 호출한다.
🤔 그렇다면 Backend 파트에서는 어떤 로직을 구현해야 할까?
정리하자면, 위와 같은 로직을 거쳐 추억 이미지를 생성하기 위해 Backend 측에서는 Chat-GPT 3.5를 활용해 Stable Diffusion의 입력 프롬프트로 사용할 "텍스트를 요약"하는 기능을 구현해야 한다!
해당 프로젝트에서는 Spring boot 환경에서 개발이 진행 중이기 때문에 Spring boot 환경에서 Chat-GPT 3.5를 API 방식으로 간편하게 활용해 텍스트를 요약하는 기능을 구현해 보자.
그럼 이제 구현에 들어가기 앞서, Chat-GPT java client 라이브러리를 활용해 Spring boot 환경에서 Chat-GPT API를 사용할 수 있도록 세팅부터 시작해 보자!
🧩 2. Spring에서 Chat-GPT java client library 사용하기
Spring 환경에서 Chat GPT java client 라이브러리를 활용해 프롬프트 엔지니어링을 구현할 수 있도록 세팅해 보자!
Spring 환경에서 GPT API를 프롬프트 엔지니어링 방식으로 사용하려면 크게 두 가지 방법이 있다. Open AI 공식 문서에서 기술해 놓은 대로 API를 그래도 사용하는 방법이 있고, Java client 라이브러리를 사용하는 방법이 있는데, 필자는 라이브러리를 활용해서 더욱 쉽게 프롬프트 엔지니어링을 구현하는 방법을 택했다.
GitHub - TheoKanning/openai-java: OpenAI Api Client in Java
OpenAI Api Client in Java. Contribute to TheoKanning/openai-java development by creating an account on GitHub.
github.com
이 라이브러리는 4k 개 이상의 star를 받고 많은 사람들이 사용하는 GPT Java client 라이브러리고, Open AI 공식 문서에도 Library 부분에 기재될 정도여서 해당 라이브러리를 활용해 프롬프트 엔지니어링 방식으로 GPT를 활용해보려 한다.
cf) Open AI 공식 문서는 https://platform.openai.com/docs/introduction로 들어가면 확인할 수 있다. Capabilities, Prompt engineering 방법, 활용할 수 있는 library 등에 대해 자세히 기록되어 있으니, 궁금한 점이 있을 때 참고하면 좋을 것 같다.
2-1. 프로젝트에서 라이브러리 세팅하기
가장 먼저, 사용하려는 라이브러리를 build.gradle에 implementation으로 추가해 import 해준다. 이때, gradle 설정 파일에 변경 사항이 생기면 Load Gradle Changes 버튼을 눌러 바로 반영하는 것 잊지 말자.
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'cohttp://m.theokanning.openai-gpt3-java:service:0.18.2'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
라이브러리의 버전은 현재 기준으로 가장 최신 버전인 0.18.2으로 선택하고, Artifact의 경우 service로 한다.
implementation 'cohttp://m.theokanning.openai-gpt3-java:service:0.18.2'
여기서 Artifact란, openai-gpt3-java: 다음에 나오는 service 부분을 의미한다.
Github에서 Read.me를 살펴보면, 해당 라이브러리는 다음과 같은 총 3가지의 Artifacts를 포함하고 있다.
api : request/response POJOs for the GPT APIs.
client : a basic retrofit client for the GPT endpoints, includes the api module
service : A basic service class that creates and calls the client. This is the easiest way to get started.
그리고 아래 설명을 보면, OpenAiService를 사용하려면 최근 버전부터는 'service' artifact를 사용하라고 돼있기 때문에 해당 프로젝트에서는 Artifact를 service로 선택해 줬다.
Please switch to using the new 'service' library if you need to use OpenAiService. The old 'client' OpenAiService is deprecated as of 0.10.0.
2-2. OpenAI API Key 발급 및 프로젝트 설정
다음으로는 Open AI API를 사용하기 위해 반드시 필요한 API Key를 발급받아야 한다. API Key 발급은 Open AI 계정을 만들고 API Keys 탭에 들어가면 생성할 수 있다. 다음 링크로 들어가면 확인할 수 있다.
API Keys 탭을 누르면 아래와 같은 화면을 볼 수 있다. 계정을 새로 만들면 phone number 인증이 안된 상태일 것이기 때문에 Start verification 버튼을 눌러 번호 인증을 받는다.


인증을 받은 후, Create new secret key를 클릭하면 위와 같은 창이 뜬다. 새로 만들 Key의 name을 입력해 주고 Create secret key 버튼을 클릭하면 성공적으로 key가 생성되고, secret key value를 화면에 보여준다.

위 설명에도 쓰여있듯이, 다른 secret key들과 마찬가지로 Open AI API secret key도 처음 생성했을 때만 key value값을 알려주고, 다시 확인할 수 없다. 잊지 말고 꼭 안전한 곳에 기록해 두자!

성공적으로 생성 완료한 후 API Keys 목록에서 방금 만든 key를 확인할 수 있다. 혹시 몰라 가려뒀지만, 실제로 KEY값에 value 전체가 쓰여있지 않고 일부만 쓰여있으니 꼭! 다른 곳에 key 값을 옮겨두자.
그리고 Open AI API를 사용하기 위해선 공식 레퍼런스에서 제공하는 것과 같이 현재 자신 계정의 API Key를 함께 요청 시 보내줘야 한다. 이제 방금 발급받은 API Key를 사용하도록 프로젝트에 설정해 주자.
아래와 같이 공식 문서에 방법이 자세히 작성되어 있다.

main/resources/application.yml
...
gpt:
token: [발급받은 API Key]
...
위 코드와 같이 main/resources 하위에 있는 프로젝트 설정 파일인 application.yml 파일에 발급받은 API Key 값을 넣어준다.
참고로, 보통 application.yml에는 온갖 secret key값들을 저장해 두기 때문에 깃허브에 공개하는 일은 없도록 하자! 만약 Open AI API Key가 공개되어 있으면 Open AI 측에서 정지 처리를 한다고 한다.
GptConfig.java
@Configuration
public class GptConfig {
@Value("${gpt.token}")
private String token;
@Bean
public OpenAiService openAiService() {
return new OpenAiService(token, Duration.ofSeconds(60));
}
}
Gpt 관련 설정을 위해 GptConfig 파일을 위와 같이 작성해 줬다. 우선 @Value 어노테이션으로 application.yml 파일에 따로 저장해 둔 open.api.token(Open API token secret key) 값을 안전하게 가져오고, OpenAiService 모듈에 전달해 생성했다.
OpenAiService service = new OpenAiService("your_token");
CompletionRequest completionRequest = CompletionRequest.builder()
.prompt("Somebody once told me the world is gonna roll me")
.model("ada")
.echo(true)
.build();
service.createCompletion(completionRequest).getChoices().forEach(System.out::println);
OpenAiService 모듈을 가져와 사용하는 방법으로 테스트해 보기 위해, 깃헙에 공개되어 있는 위 테스트 코드 형식을 가져와 GptTestController를 만들었다. 구조는 아래와 같다.
GptTestController.java
@RestController
@RequestMapping("/api/gpt3")
public class GptTestController {
private final OpenAiService openAiService;
public GptTestController(OpenAiService openAiService) {
this.openAiService = openAiService;
}
@PostMapping("/request")
public ResponseEntity<List<CompletionChoice>> sendRequest() {
CompletionRequest completionRequest = CompletionRequest.builder()
.prompt("This is test prompt!")
.model("ada")
.echo(false)
.build();
// service.createCompletion(completionRequest).getChoices().forEach(System.out::println);
return ResponseEntity.ok(openAiService.createCompletion(completionRequest).getChoices());
}
}
prompt값에 넣어준 내용은 gpt에게 전달하는 메시지 값이고, echo를 통해 완료 후 프롬프트 반복 여부를 설정할 수 있다.
이제 "/api/gpt3/request" 엔드포인트로 POST 요청을 보내면, CompletionRequest.builder()를 통해 CompletionRequest 객체를 만들어 OpenAI 서비스에 요청을 보내고, 반환받은 결과를 ResponseEntity로 감싸 최종 반환하게 된다.
[
{
"text": "This is the completion of your test prompt!",
"index": 0,
"logprobs": null,
"finish_reason": "length"
}
]
요청을 보내보면, 위와 같은 JSON 반환값을 확인할 수 있다.
이렇게 하면 드디어 Spring boot 환경에서 Java Client 라이브러리를 활용해 성공적으로 Chat-GPT와 연결된 것이다!
그럼 이제 본격적으로 Chat-GPT에 요약하려는 텍스트 여러 개를 담아 요청을 보내, 텍스트를 요약하는 기능을 구현해 보자!
🧩 3. Chat-GPT로 요약 기능 구현하기
앞서 프로젝트 소개에서 정리했듯이, 이번에 구현하려는 기능은 입력된 텍스트를 요약하는 기능이다.
요약하려는 input 텍스트들이 여러 개가 존재하고, 그 텍스트들을 request에 담아 Chat-GPT를 호출하면 Chat-GPT가 요약한 텍스트를 반환하는 로직을 구현해 보자!
3-1. 환경 설정하기
가장 먼저, Chat GPT API와의 연동을 위해 필요한 설정을 프로젝트 상에서 구성해야 한다. GptConfig와 GptProperties 클래스를 통해 설정해 주면 되는데, GptConfig는 앞서 테스트 코드를 작성할 때와 동일하게 작성해 주면 된다.
GptConfig.java
@Configuration
@RequiredArgsConstructor
public class GptConfig {
private final GptProperties chatProperties;
@Bean
public OpenAiService openAiService() {
return new OpenAiService(chatProperties.getToken(), Duration.ofSeconds(60));
}
}
GptConfig 클래스는 @Configuration annotation으로 인해 스프링 설정 클래스로 등록되고, @Bean annotation에 의해 OpenAiService 빈이 생성된다. 이때, OpenAiService는 Chat GPT API와의 통신을 담당하게 된다. 여기서 final로 선언한 chatProperties를 통해 앞서 [2.1 OpenAI API Key 발급 및 프로젝트 설정] 파트에서 발급받은 API 토큰을 주입받아 사용하게 된다.
GptProperties.java
@Component
public class GptProperties {
@Value("${gpt.token}")
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
GptProperties 클래스는 @Component annotation을 통해 스프링 빈으로 등록되며, @Value 어노테이션을 사용해 application.yml 파일에 설정된 gpt.token 값을 주입받는다.
3-2. Request, Response Dto 정의하기
다음으로, Chat GPT API에 요청할 때 사용할 Dto와 응답받을 Dto를 정의해줘야 한다. 내가 원하는 형식으로 요청을 보내고 받을 수 있도록 하는 단계이다.
GptRequest.java
public record GptRequest(String model, List<ChatMessage> messages, Double temperature) {
private static String prefixMessage =
"이 글들은 한 추억에 대해 여러명이 작성한 글이야. 형태를 보면 큰따옴표와 쉼표로 한 명이 작성한 글을 분리한거야. 즉, 큰따옴표 안에 들어간 글이 한 명이 작성한 글이라는 뜻이야."
+ "이 글들을 잘 정리해서 100자 정도의 하나의 글로 만들어서 한글과 영어로 각각 작성해줘. 근데 잘 알아야 할 게 이 글들을 작성한 사람들이 다 같이 함께한 일이기 때문에 글을 쓸 때 주체를 우리라고 해야해. 우리 이 때 이랬는데 이런 느낌으로 말이야. 모든 내용이 적절하게 다 들어갔으면 좋겠어. 그냥 이어붙이는게 아니라 적절히 섞어서 새로운 글로 정리해달라는거야. "
//+ "그리고 한글로 작성한 글이랑, 영어로 작성한 글 두 개를 다 반환해줘."
+ "반환할 때 형식은 한글로 작성한 글을 괄호로 묶고 영어로 작성한 글은 또 따로 괄호로 묶어서 보내줘."
+ "(한글로 작성한 글 100자), (영어로 작성한 글 100자) 이 형식으로 보내달라는 말이야. 각 글을 괄호로 묶고 쉼표로 구분해줘."
+ "다른 부가적인 말 하지 말고 딱 정리한 글만 반환해줘. 그리고 말투를 일기 쓴 것처럼 ~했어. 느낌으로 작성해줘. 문장이 좀 자연스럽게 이어지게 해줄래? 전체가 하나의 일기처럼 느껴지게 써줘. 그리고 글 안에 따옴표나 줄바꿈 문자 넣지 마."
+ "전체적으로 구어체 느낌으로 ~다. 말고 ~했었어. 말투로 작성해줘. 말이 좀 이어지게 해서 일기를 작성한다고 생각해."
//+ "한글로 작성된 글이랑 영어로 작성된 글을 반드시 줄바꿈 문자로 구분해서 보내줘."
;
public static ChatCompletionRequest from(String inputText) {
return ChatCompletionRequest.builder()
.model("gpt-3.5-turbo")
.messages(List.of(new ChatMessage("user", prefixMessage + inputText)))
.temperature(0.7)
.build();
}
}
먼저, GptRequest 클래스는 Chat GPT API에 요청을 보낼 때 사용할 Dto이다. prefixMessage는 request 앞에 추가되는 메시지로, 요약의 조건과 형식을 지정하는 역할을 해준다. from 메서드는 입력된 텍스트인 inputText를 전달받아 ChatCompletionRequest 객체로 변환하는 메서드다.
GptResponse.java
public record GptResponse(List<Message> messages) {
public static GptResponse of(ChatCompletionResult result) {
return new GptResponse(
result.getChoices().stream()
.map(completionChoice -> new Message(completionChoice.getMessage().getRole(),
completionChoice.getMessage().getContent()))
.collect(Collectors.toList())
);
}
public record Message(String role, String message) {
public static Message of(ChatMessage chatMessage) {
return new Message(chatMessage.getRole(), chatMessage.getContent());
}
}
public record Usage(Long promptTokens, Long completionTokens, Long totalTokens) {
public static Usage of(com.theokanning.openai.Usage usage) {
return new Usage(usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens());
}
}
}
GptResponse 클래스는 Chat GPT API로부터의 응답을 처리하는 Dto이다. of 메서드는 ChatCompletionResult 타입인 응답 객체를 전달 받아 GptResponse 객체로 변환하는 작업을 수행한다. 여기서 Message 레코드는 API 응답에서 메시지 데이터를 담고 있으며, Usage 레코드는 API 사용량 정보를 가지고 있다.
3-3. 서비스 로직 구현하기
요청 및 응답을 담을 Dto까지 모두 구현했으니, Chat GPT API와의 상호작용을 담당하는 서비스 로직을 구현할 차례!
먼저, 클라이언트로부터 입력값을 담은 요청을 받는 RESTful 한 형태의 Controller를 구성해 보자.
GptController.java
@RestController
@AllArgsConstructor
@RequestMapping("/gpt3")
public class GptController {
private final GptService gptService;
@PostMapping("/completePuzzle")
public BaseResponse<CompletePuzzleResponse> completePuzzle(@RequestBody CompletePuzzleRequest completePuzzleRequest) {
GptResponse response = gptService.completion(gptService.toText(completePuzzleRequest.puzzleTextList()));
return new BaseResponse<>(gptService.parseResponse(response.messages().get(0).message()));
}
}
GptController 클래스는 클라이언트로부터 텍스트들을 List<String> 타입으로 입력받아 Service단으로 전달한 후 로직 수행 후 요약한 텍스트를 반환하는 역할을 한다.
CompletePuzzleRequest.java
public record CompletePuzzleRequest(List<String> puzzleTextList) {}
코드를 보면, 클라이언트로부터 List<String> 타입인 puzzleTextList를 입력받은 다음, gptService의 toText() 메서드로 List 형식인 입력값을 하나의 string으로 합친다. 그 후, gptService의 completion() 메소드를 통해 Chat-GPT를 호출해 텍스트 요약을 수행한 후 결과 텍스트를 다시 GptController에 반환한다. parseResponse 메소드를 통해 전달받은 결과를 파싱해 최종 결과를 반환한다.
서비스의 메인 로직을 담당하는 GptService는 아래와 같이 구현했다.
GptService.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class GptService {
private final OpenAiService openAiService;
@Transactional
public GptResponse completion(String inputText) {
ChatCompletionResult chatCompletion = openAiService.createChatCompletion(GptRequest.from(inputText));
return GptResponse.of(chatCompletion);
}
public String toText(List<String> puzzleTextList) {
StringBuilder result = new StringBuilder();
int index = 0;
for (String puzzleText : puzzleTextList) {
result.append("\"").append(puzzleText).append("\"");
if (index < puzzleTextList.size() - 1) {
result.append(", ");
}
index++;
}
return result.toString();
}
public CompletePuzzleResponse parseResponse(String response) {
// 영어 부분 추출을 위한 정규 표현식 패턴 (괄호로 묶인 부분)
Pattern pattern = Pattern.compile("\\((.*?)\\)");
Matcher matcher = pattern.matcher(response);
String englishPart = "";
if (matcher.find()) {
// 괄호 안의 영어 부분 추출
englishPart = matcher.group(1);
}
// 영어 부분을 제외한 나머지 문자열 (한글 부분)
String koreanPart = response.replace("(" + englishPart + ")", "").trim();
// 추출된 한글 부분과 영어 부분으로 CompletePuzzleResponse 객체 생성
return new CompletePuzzleResponse(englishPart, koreanPart);
}
}
크게 3가지의 메소드로 구성되어 있는데, 바로 completion(), toText(), parseResponse()다.
completion 메소드는 입력값 텍스트를 OpenAiService를 통해 GPT 모델에 전달하고, 생성된 결과를 반환하는 역할을 한다.
toText 메소드는 List<String> 타입의 입력값을 하나의 string으로 합치는 역할을 한다. 이 메소드를 통해 생성한 하나의 string이 GptController를 거쳐 completion 메소드로 전달되어 GPT 모델에 입력되는 것이다.
parseResponse 메소드는 GPT 모델이 반환한 응답을 파싱해 필요한 정보만 추출하는 역할을 한다.
해당 서비스에서는 Stable Diffusion의 입력값으로 사용하기 위해 영어로 작성된 텍스트도 필요했고, 사용자에게 반환해 피드에 나타내기 위해서 한글로 작성된 텍스트도 필요했다.
이러한 이유로 문자열에서 특정 패턴을 찾아내는 정규 표현식을 사용해 영어로 작성된 부분과 한글로 작성된 부분을 분리하는 로직을 추가로 구현했다.
3-4. 응답 결과 확인하기!
드디어! Spring boot 환경에서 Chat-GPT API로 텍스트를 요약하는 기능을 간단히 구현하는 데 성공했다.
이제 제대로 요청이 가는지 Postman으로 확인해 보자!

위와 같이 요청이 오면 성공이다. Response 형태는 어떻게 구현하는지에 따라 많이 달라지는데, 평소에 BaseResponse라는 공통 response 클래스를 만들어 사용하기 때문에 위와 같이 isSuccess, message, result로 구성되어 출력된다.
Chat-GPT가 요약한 텍스트값이 "text" 부분에 잘 들어가 있는 걸 확인할 수 있다.
🧩 마무리하며
이렇게 하면 프로젝트에서 구현하려던 텍스트 요약 기능을 성공적으로 구현 완료했다. 라이브러리를 잘 활용하면 훨씬 더 수월하게 원하는 기능을 구현할 수 있으니, 한 번 사용해 보는 것을 추천한다!
GitHub - TheoKanning/openai-java: OpenAI Api Client in Java
OpenAI Api Client in Java. Contribute to TheoKanning/openai-java development by creating an account on GitHub.
github.com
"Neverland"를 개발하는 과정에서 Open AI의 Chat-GPT API를 사용하기 위한 Java 라이브러리 세팅하는 방법과 간단한 요약 기능을 구현하는 과정을 한 번 정리해 봤다. 이제 졸업 프로젝트의 주요 기능 구현을 마쳤고 프로젝트 구현도 마무리를 지었으니, 다음에는 프로젝트 회고를 남겨 볼 예정이다.