⭐️ 목적
현재 진행 중인 캡스톤 프로젝트에 핵심 기능 중 하나가 AI를 이용한 자동 답변 기능이다.
ChatGPT API를 활용하여 답변 생성을 하고자 했는데, AI 서버를 분리한 상태이기 때문에
스프링부트 프로젝트에 연결해서 자동 답변 기능을 구현해야 했다.
✅ 구현 과정 (간략)
# 연결 로직 구상
> 첫 번째 아이디어: 즉시 응답
단순하게, 질문글 작성을 요청할 경우 글의 내용을 AI 서버에 전송하여,
ChatGPT API를 이용하여 답변을 생성하고 해당 답변을 응답으로 받아 작성하는 방식이다.
하지만, 이런 방식으로 구현할 경우 챗 지피티의 답변 생성 시간만큼 질문글 생성에 딜레이가 생기게 되고,
응답 시간의 딜레이가 생긴다는 것은 서비스 측면에서 일어나선 안 될 일이기 때문에 개선이 필요했다.
> 두 번재 아이디어: 추후 응답
이 방법 또한 자동 답변 요청 시점은 질문글 작성 요청 순간으로 같다.
하지만 딜레이 발생을 최소화하기 위해 AI 서버측에 요청을 보내고 난 후 AI 서버에서 답변을 따로 생성을 하고,
답변이 생성 되고나면 서비스 서버쪽으로 답변글 작성 요청을 보내는 방식이다.
답변글 작성 API는 이미 구현해뒀기 때문에 이 API에 생성된 답변 내용을 담아 요청을 보내면
추가 API를 생성할 필요도 없고, 딜레이를 최소화하여 질문글을 작성할 수 있기 때문에 이 방식을 채택했다.
이 과정을 간단한 흐름도로 표현해 보면 다음과 같다.
# Feign Client 구현
AI 서버에 답변 생성 요청을 위해 이전에 디스코드 웹훅 연결 시 활용했던 Feign Client를 이용하여 구현했다.
클라이언트는 다음과 같이 구현했다.
@FeignClient(name = "${ai.bot.name}", url = "${ai.bot.endpoint}")
public interface AIBotClient {
@PostMapping
void sendAutoAnswerRequest(@RequestBody AutoAnswerRequest request);
}
✅ 트러블 슈팅
# AI가 답변글 작성 API를 어떻게 사용해야할까?
서비스에 jwt를 이용하여 특정 API를 요청할 시 권한에 필터링 되도록 설정해놨다.
AI 서버에서 답변글 작성을 위해선 이 점을 해결해야 했기 때문에 추가적인 고민이 필요했다.
> 첫 번째 구현: 질문을 요청한 계정 정보를 활용해서 엑세스 토큰 발급
현재 서비스에는 고등학생, 대학생 두 가지 회원 타입이 존재한다.
질문글은 고등학생 회원, 답변글은 대학생 회원만 작성이 가능하도록 구현해두었다.
즉, 답변글을 작성하기 위해선 대학생 타입의 엑세스 토큰과 함께 작성 API를 요청해야만 한다.
나는 구현 방식을 고민하다가 이 점을 망각한 채 어떻게 든 임시 엑세스 토큰을 발급할 방법만 찾아당겼고,
질문글 작성을 요청한 고등학생 계정 정보를 이용하여 엑세스 토큰을 생성하고 AI 서버로 질문 내용과 함께 전송하는 방식으로 구현했다.
나름 괜찮은 아이디어라고 생각한 채 신나서 PR 후 배포를 진행했고,
권한 없는 유저의 요청이라는 오류 메시지를 보게되었다.
> 두 번째 구현: AI용 계정을 따로 생성하여 활용
결국 임시 엑세스 토큰을 생성할 수 있는 대학생 타입의 계정이 필요하다고 판단했다.
(답변 작성시 나타나는 계정 문제도 있었다)
대학생 타입을 가진 AI 더미 계정을 따로 생성해야겠다는 결론을 내렸고,
어떤 방식으로 AI 계정을 데이터베이스에 저장해 둘지 고민해야했다.
처음에는 수동으로 RDS의 데이터베이스에 저장할까 했지만,
데이터베이스 초기화가 일어날 때마다 수동으로 데이터를 추가하는 것은 별로라는 생각이 들어 ApplicationRunner를 활용하기로 했다.
ApplicationRunner 인터페이스를 extend하고 run 메소드를 오버라이드하여,
원하는 코드를 적으면 해당 코드가 스프링부트 서버 시작 시 동작하도록 구현할 수 있다.
@Component
public class TestRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 원하는 동작
}
}
이를 이용해서 서버를 시작할 때 멤버에 AI 계정이 존재하지 않으면,
이메일 인증 때문에 만들었던 팀 구글 계정을 사용해서 AI 계정을 생성하고 데이터베이스에 저장하도록 구현했다.
이제 질문글 작성시 AI 계정을 활용해서 엑세스 토큰을 생성하고 자동 답변 요청시 포함시키도록 로직을 변경했다.
# 자동 답변 글이 안 써진다
질문글을 생성하면 제대로 된 엑세스 토큰과 질문 내용을 포함시킨 자동 답변 요청이 AI 서버로 전달됐지만,
AI 서버에서 답변을 생성하고 답변글 작성 요청을 하면 존재하지 않는 질문글이라는 오류가 발생했다.
(답변글은 질문글에 달리는 것이기 때문에 자동 답변 생성 요청시 질문글의 id를 포함시켜 전송한다)
로직상에 문제는 보이지 않았고, 데이터 또한 원하는데로 정상적으로 전달됐지만 답변글 작성만 작동하지 않았다.
한참을 AI 파트와 상의한 끝에 AI 서버 구현 코드 문제라고 판단했고
코드를 전달받아 확인해 본 결과 답변 생성 요청을 받은 후 즉시 200 응답을 보낸 후 백그라운드에서 생성해야 했지만,
그 부분이 수정이 누락되어 문제가 발생한 것이었다.
응답을 즉시 전달하지 않고 답변 생성 후 답변글 작성 요청을 서비스로 한 후 응답을 보내주기 때문에
그 시간동안 서비스 측은 블록킹 되어 글 작성이 완료되지 않았고,
작성이 완료되지 않은 질문글 ID로 답변 생성을 요청하여 질문글이 존재하지 않는다는 오류가 발생한 것이었다.
문제의 부분을 쓰레드를 이용해서 답변 생성을 백그라운드로 하는 방식으로 리펙토링 했고,
원하는 동작을 성공적으로 완성시킬 수 있었다.
✅ 추후 개선점
# 질문글 작성시 자동 답변 요청을 하는 방식에는 문제가 많다
현재 AI측 답변글 생성 요청 전에 질문글 생성이 선행된다는 로직이 존재하지 않기 때문에
만약 ChatGPT 등의 AI들이 너무 일을 빨리하거나 서비스 서버측이 모종의 이유로 퍼포먼스가 떨어지는 경우,
이전과 같은 질문글을 찾지 못하는 문제가 발생하게 된다. (그럴 일은 거의 없을 것이라 생각하긴 한다)
현재 생각 중인 로직 변경 방식은 두 가지가 있다.
1. 임시 방식:
AI 서버 측 코드에 작성 요청에 대해 응답 코드가 온 경우에는 최대 x번의 리트라이를 하도록 구현한다.
2. 목표 방식:
현재 방식은 매 질문글 작성 때마다 챗 지피티 API를 호출하기 때문에 비용상의 문제가 발생할 것이다.
질문글 작성 후 특정 시간이 지나도 답변이 달리지 않는 질문글을 대상으로 자동 답변이 작성도록 구현한다.
목표긴 하지만 두 번째 방식대로 구현만 성공하면, 비용 문제와 글 작성 순서 문제 모두 해결할 수 있다.
현재는 임시 방식을 이용해서 글 작성 순서 문제를 해결하도록 할 것이다.
✅ 공부 내용
# 별거 아니긴 하지만 ApplicationRunner 선택 이유
스프링부트에서 서버 동작 시 실행할 코드를 지정하는 방법에는 CommandLineRunner와 ApplicationRunner가 있다.
두 인터페이스는 Runner 인터페이스를 상속하며 run 메소드를 사용하는 형태가 동일하지만,
CommandLineRunner는 스트링 타입의 가변 인자만 받을 수 있지만
ApplicationRunner는 다양한 타입의 가변 인자를 받을 수 있다는 차이점이 존재한다.
사실 이번 구현에서는 인자를 활용할 일이 없어서 두 인터페이스 모두 상관 없었지만,
이왕 사용하는거 좀 더 유틸성이 좋은 ApplicationRunner를 사용하게 되었다.
여담으로 ApplicationRunner의 경우 스프링부트 서버 실행 완료 후에 run 메소드를 호출하는데,
서버 실행 완료 시점은 SpringApplication.run() 실행이 완료 된 시점으로 판단한다고 한다.
(서버는 SpringApplication.run() 완료 후 트래픽을 받는 등의 동작이 가능하게 된다고 한다)
'스프링부트 메모' 카테고리의 다른 글
[스프링부트] Builder 패턴에 대해 알게 된 점 (0) | 2024.11.10 |
---|---|
[스프링부트] GenerationType.IDENTITY vs GenerationType.SEQUENCE (0) | 2024.11.07 |
[스프링부트] 이메일 전송 기능 구현하기 (0) | 2024.09.23 |
[스프링부트] 디스코드 웹훅 연동하기 (1) | 2024.09.22 |
[스프링부트] 메모 시작 (0) | 2024.09.22 |