Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/com/sofa/linkiving/LinkivingApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@SpringBootApplication
@EnableJpaAuditing
@EnableFeignClients(basePackages = "com.sofa.linkiving.infra.feign")
@EnableFeignClients
public class LinkivingApplication {

public static void main(String[] args) {
Expand Down
10 changes: 0 additions & 10 deletions src/main/java/com/sofa/linkiving/domain/chat/ai/AiTitleClient.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.sofa.linkiving.domain.chat.ai;

import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Primary
public class MockAiTitleClient implements AiTitleClient {
@Profile("test")
public class MockTitleClient implements TitleClient {
@Override
public String generateSummary(String firstChat) {
public String generateTitle(String firstChat) {
return String.format("임시 제목[%s]", firstChat);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.sofa.linkiving.domain.chat.ai;

import java.util.List;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import com.sofa.linkiving.domain.chat.dto.request.TitleGenerateReq;
import com.sofa.linkiving.domain.chat.dto.response.TitleGenerateRes;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
@Profile("!test")
@RequiredArgsConstructor
public class RagTitleClient implements TitleClient {

private static final int MAX_TITLE_LENGTH = 100;
private final RagTitleFeign ragTitleFeign;

@Override
public String generateTitle(String firstChat) {
try {
List<TitleGenerateRes> response = ragTitleFeign.generateTitle(new TitleGenerateReq(firstChat));

if (response == null || response.isEmpty()) {
return truncateTitle(firstChat);
}

return response.get(0).title();

} catch (Exception e) {
log.error("AI 서버 통신 실패. 기본 제목으로 대체합니다. error={}", e.getMessage());
return truncateTitle(firstChat);
}
}

private String truncateTitle(String originalTitle) {
if (originalTitle.length() <= MAX_TITLE_LENGTH) {
return originalTitle;
}
return originalTitle.substring(0, MAX_TITLE_LENGTH);
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/sofa/linkiving/domain/chat/ai/RagTitleFeign.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sofa.linkiving.domain.chat.ai;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import com.sofa.linkiving.domain.chat.dto.request.TitleGenerateReq;
import com.sofa.linkiving.domain.chat.dto.response.TitleGenerateRes;
import com.sofa.linkiving.infra.feign.GlobalFeignConfig;

@FeignClient(name = "ai-title-client", url = "${ai.server.url}", configuration = GlobalFeignConfig.class)
public interface RagTitleFeign {
@PostMapping("/webhook/title-generate")
List<TitleGenerateRes> generateTitle(@RequestBody TitleGenerateReq request);
}
10 changes: 10 additions & 0 deletions src/main/java/com/sofa/linkiving/domain/chat/ai/TitleClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sofa.linkiving.domain.chat.ai;

public interface TitleClient {
/**
* AI 서버에 첫 채팅 내용을 토대로 채팅방 제목 생성 요청을 보냅니다.
* @param firstChat 채팅 시작 대화
* @return 제목
*/
String generateTitle(String firstChat);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sofa.linkiving.domain.chat.dto.request;

public record TitleGenerateReq(
String firstMessage
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sofa.linkiving.domain.chat.dto.response;

public record TitleGenerateRes(
String title
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sofa.linkiving.domain.chat.ai.AiTitleClient;
import com.sofa.linkiving.domain.chat.ai.TitleClient;
import com.sofa.linkiving.domain.chat.dto.internal.MessagesDto;
import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.dto.response.CreateChatRes;
Expand All @@ -25,7 +25,7 @@ public class ChatFacade {
private final ChatService chatService;
private final MessageService messageService;
private final FeedbackService feedbackService;
private final AiTitleClient aiTitleClient;
private final TitleClient titleClient;

public MessagesRes getMessages(Member member, Long chatId, Long lastId, int size) {
Chat chat = chatService.getChat(chatId, member);
Expand All @@ -35,7 +35,7 @@ public MessagesRes getMessages(Member member, Long chatId, Long lastId, int size

@Transactional
public CreateChatRes createChat(String firstChat, Member member) {
String title = aiTitleClient.generateSummary(firstChat);
String title = titleClient.generateTitle(firstChat);
Chat chat = chatService.createChat(title, member);

return CreateChatRes.from(chat, firstChat);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.sofa.linkiving.domain.chat.ai;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.util.Collections;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.sofa.linkiving.domain.chat.dto.request.TitleGenerateReq;
import com.sofa.linkiving.domain.chat.dto.response.TitleGenerateRes;

@ExtendWith(MockitoExtension.class)
public class RagTitleClientTest {

@InjectMocks
private RagTitleClient ragTitleClient;

@Mock
private RagTitleFeign ragTitleFeign;

@Test
@DisplayName("AI 서버 통신 성공 시 생성된 제목을 반환한다")
void shouldReturnGeneratedTitleWhenApiCallSucceeds() {
// given
String firstChat = "안녕하세요";
String generatedTitle = "인사말";

TitleGenerateRes resDto = mock(TitleGenerateRes.class);
given(resDto.title()).willReturn(generatedTitle);

given(ragTitleFeign.generateTitle(any(TitleGenerateReq.class)))
.willReturn(List.of(resDto));

// when
String result = ragTitleClient.generateTitle(firstChat);

// then
assertThat(result).isEqualTo(generatedTitle);
verify(ragTitleFeign).generateTitle(any(TitleGenerateReq.class));
}

@Test
@DisplayName("AI 서버 응답이 빈 리스트일 경우 요청한 첫 메시지(firstChat)를 그대로 반환한다")
void shouldReturnFirstChatWhenResponseIsEmpty() {
// given
String firstChat = "안녕하세요";

given(ragTitleFeign.generateTitle(any(TitleGenerateReq.class)))
.willReturn(Collections.emptyList());

// when
String result = ragTitleClient.generateTitle(firstChat);

// then
assertThat(result).isEqualTo(firstChat);
}

@Test
@DisplayName("AI 서버 응답이 null일 경우 요청한 첫 메시지(firstChat)를 그대로 반환한다")
void shouldReturnFirstChatWhenResponseIsNull() {
// given
String firstChat = "안녕하세요";

given(ragTitleFeign.generateTitle(any(TitleGenerateReq.class)))
.willReturn(null);

// when
String result = ragTitleClient.generateTitle(firstChat);

// then
assertThat(result).isEqualTo(firstChat);
}

@Test
@DisplayName("AI 서버 통신 중 예외 발생 시 로그를 남기고 첫 메시지(firstChat)를 그대로 반환한다")
void shouldReturnFirstChatWhenExceptionOccurs() {
// given
String firstChat = "안녕하세요";

given(ragTitleFeign.generateTitle(any(TitleGenerateReq.class)))
.willThrow(new RuntimeException("API Connection Failed"));

// when
String result = ragTitleClient.generateTitle(firstChat);

// then
assertThat(result).isEqualTo(firstChat);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.sofa.linkiving.domain.chat.ai.AiTitleClient;
import com.sofa.linkiving.domain.chat.ai.TitleClient;
import com.sofa.linkiving.domain.chat.dto.internal.MessagesDto;
import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.dto.response.CreateChatRes;
Expand All @@ -39,7 +39,7 @@ public class ChatFacadeTest {
private FeedbackService feedbackService;

@Mock
private AiTitleClient aiTitleClient;
private TitleClient titleClient;

@Mock
private Member member;
Expand Down Expand Up @@ -105,7 +105,7 @@ void shouldReturnCreateChatResWhenCreateChat() {
given(mockChat.getId()).willReturn(chatId);
given(mockChat.getTitle()).willReturn(generatedTitle);

given(aiTitleClient.generateSummary(firstChat)).willReturn(generatedTitle);
given(titleClient.generateTitle(firstChat)).willReturn(generatedTitle);
given(chatService.createChat(generatedTitle, member)).willReturn(mockChat);

// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sofa.linkiving.domain.chat.ai.AiTitleClient;
import com.sofa.linkiving.domain.chat.ai.TitleClient;
import com.sofa.linkiving.domain.chat.dto.request.CreateChatReq;
import com.sofa.linkiving.domain.chat.dto.response.MessageRes;
import com.sofa.linkiving.domain.chat.dto.response.MessagesRes;
Expand Down Expand Up @@ -73,7 +73,7 @@ public class ChatApiIntegrationTest {
private SummaryRepository summaryRepository;

@Autowired
private AiTitleClient aiTitleClient;
private TitleClient titleClient;

@Autowired
private ChatFacade chatFacade;
Expand Down Expand Up @@ -186,7 +186,7 @@ void shouldCreateChatSuccessfullyWhenValidRequest() throws Exception {
// given
String firstChatContent = "AI 관련 최신 뉴스 알려줘";
CreateChatReq req = new CreateChatReq(firstChatContent);
String title = aiTitleClient.generateSummary(firstChatContent);
String title = titleClient.generateTitle(firstChatContent);

// when & then
mockMvc.perform(post(BASE_URL)
Expand Down Expand Up @@ -248,6 +248,7 @@ void shouldReturnChatListWhenGetChats() throws Exception {
.title("Chat 2")
.build());

// 생성 시간 차이를 두기 위해 잠시 대기 (정렬 테스트)
Thread.sleep(10);

Chat chat1 = chatRepository.save(Chat.builder()
Expand Down