Skip to content

Commit

Permalink
Feat: 요약 및 문제 생성 API 구현 (#32)
Browse files Browse the repository at this point in the history
* Rename: AI Task 도메인 이름 변경

- llm 으로 변경

* Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리

* Feat: LLM 작업 진행 상태 확인 기능 구현

* Feat: 요약 및 문제 결과 조회 기능 구현

* Feat: 요약 및 문제 생성 기능 구현

- AI 서버와 통신하는 부분 제외하고 기능 구현
- 임시 UUID 를 통해 task 저장

* Feat: LLM 서버 콜백 기능 구현

- LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달
- task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트

* Refactor: 변수 이름 변경

- 필드명 카멜케이스로 변경

* Refactor: 통일성 없는 부분 수정

- 필드명 변경
- 변수 추출

* Refactor: 예외 종류, 메서드 네이밍 변경

- LLMQueryService 예외 타입 변경
- SummaryAndProblemUpdateResponse 메서드 네이밍 변경

* Refactor: LLMQueryService 응답과 LLMController 응답 분리
  • Loading branch information
hynseoj authored Sep 27, 2024
1 parent 4e72d91 commit 31cf7a8
Show file tree
Hide file tree
Showing 34 changed files with 928 additions and 140 deletions.
38 changes: 0 additions & 38 deletions src/main/java/notai/aiTask/application/AITaskService.java

This file was deleted.

10 changes: 0 additions & 10 deletions src/main/java/notai/aiTask/application/command/AITaskCommand.java

This file was deleted.

7 changes: 0 additions & 7 deletions src/main/java/notai/aiTask/domain/AITaskRepository.java

This file was deleted.

28 changes: 0 additions & 28 deletions src/main/java/notai/aiTask/presentation/AITaskController.java

This file was deleted.

18 changes: 0 additions & 18 deletions src/main/java/notai/aiTask/presentation/request/AITaskRequest.java

This file was deleted.

This file was deleted.

110 changes: 110 additions & 0 deletions src/main/java/notai/llm/application/LLMQueryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package notai.llm.application;

import lombok.RequiredArgsConstructor;
import notai.common.exception.type.BadRequestException;
import notai.common.exception.type.InternalServerErrorException;
import notai.common.exception.type.NotFoundException;
import notai.document.domain.DocumentRepository;
import notai.llm.application.result.LLMResultsResult;
import notai.llm.application.result.LLMResultsResult.LLMContent;
import notai.llm.application.result.LLMResultsResult.LLMResult;
import notai.llm.application.result.LLMStatusResult;
import notai.llm.domain.TaskStatus;
import notai.llm.query.LLMQueryRepository;
import notai.problem.query.ProblemQueryRepository;
import notai.problem.query.result.ProblemPageContentResult;
import notai.summary.query.SummaryQueryRepository;
import notai.summary.query.result.SummaryPageContentResult;
import org.springframework.stereotype.Service;

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

import static notai.llm.domain.TaskStatus.COMPLETED;
import static notai.llm.domain.TaskStatus.IN_PROGRESS;

@Service
@RequiredArgsConstructor
public class LLMQueryService {

private final LLMQueryRepository llmQueryRepository;
private final DocumentRepository documentRepository;
private final SummaryQueryRepository summaryQueryRepository;
private final ProblemQueryRepository problemQueryRepository;

public LLMStatusResult fetchTaskStatus(Long documentId) {
checkDocumentExists(documentId);
List<Long> summaryIds = getSummaryIds(documentId);
List<TaskStatus> taskStatuses = getTaskStatuses(summaryIds);

int totalPages = summaryIds.size();
int completedPages = Collections.frequency(taskStatuses, COMPLETED);

if (totalPages == completedPages) {
return LLMStatusResult.of(documentId, COMPLETED, totalPages, completedPages);
}
return LLMStatusResult.of(documentId, IN_PROGRESS, totalPages, completedPages);
}

public LLMResultsResult findTaskResult(Long documentId) {
checkDocumentExists(documentId);
List<SummaryPageContentResult> summaryResults = getSummaryPageContentResults(documentId);
List<ProblemPageContentResult> problemResults = getProblemPageContentResults(documentId);
checkSummaryAndProblemCountsEqual(summaryResults, problemResults);

List<LLMResult> results = summaryResults.stream().map(summaryResult -> {
LLMContent content = LLMContent.of(
summaryResult.content(),
findProblemContentByPageNumber(problemResults, summaryResult.pageNumber())
);
return LLMResult.of(summaryResult.pageNumber(), content);
}).toList();

return LLMResultsResult.of(documentId, results);
}

private void checkDocumentExists(Long documentId) {
if (!documentRepository.existsById(documentId)) {
throw new NotFoundException("해당 강의자료를 찾을 수 없습니다.");
}
}

private static void checkSummaryAndProblemCountsEqual(
List<SummaryPageContentResult> summaryResults, List<ProblemPageContentResult> problemResults
) {
if (summaryResults.size() != problemResults.size()) {
throw new InternalServerErrorException("AI 요약 및 문제 생성 중에 문제가 발생했습니다."); // 요약 개수와 문제 개수가 불일치
}
}

private List<Long> getSummaryIds(Long documentId) {
List<Long> summaryIds = summaryQueryRepository.getSummaryIdsByDocumentId(documentId);
if (summaryIds.isEmpty()) {
throw new BadRequestException("AI 기능을 요청한 기록이 없습니다.");
}
return summaryIds;
}

private List<TaskStatus> getTaskStatuses(List<Long> summaryIds) {
return summaryIds.stream().map(llmQueryRepository::getTaskStatusBySummaryId).toList();
}

private List<SummaryPageContentResult> getSummaryPageContentResults(Long documentId) {
List<SummaryPageContentResult> summaryResults = summaryQueryRepository.getPageNumbersAndContentByDocumentId(
documentId);
if (summaryResults.isEmpty()) {
throw new NotFoundException("AI 기능을 요청한 기록이 없습니다.");
}
return summaryResults;
}

private List<ProblemPageContentResult> getProblemPageContentResults(Long documentId) {
return problemQueryRepository.getPageNumbersAndContentByDocumentId(documentId);
}

private String findProblemContentByPageNumber(List<ProblemPageContentResult> results, int pageNumber) {
return results.stream().filter(result -> result.pageNumber() == pageNumber).findFirst().map(
ProblemPageContentResult::content).orElseThrow(() -> new InternalServerErrorException(
"AI 요약 및 문제 생성 중에 문제가 발생했습니다.")); // 요약 페이지와 문제 페이지가 불일치
}
}
76 changes: 76 additions & 0 deletions src/main/java/notai/llm/application/LLMService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package notai.llm.application;

import lombok.RequiredArgsConstructor;
import notai.common.exception.type.NotFoundException;
import notai.document.domain.Document;
import notai.document.domain.DocumentRepository;
import notai.llm.application.command.LLMSubmitCommand;
import notai.llm.application.command.SummaryAndProblemUpdateCommand;
import notai.llm.application.result.LLMSubmitResult;
import notai.llm.domain.LLM;
import notai.llm.domain.LLMRepository;
import notai.problem.domain.Problem;
import notai.problem.domain.ProblemRepository;
import notai.summary.domain.Summary;
import notai.summary.domain.SummaryRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.UUID;

/**
* SummaryService 와 ExamService 는 엔티티와 관련된 로직만 처리하고
* AI 요약 및 문제 생성 요청은 여기서 처리하는 식으로 생각했습니다.
* AI 서버와의 통신은 별도 클래스에서 처리합니다.
*/
@Service
@Transactional
@RequiredArgsConstructor
public class LLMService {

private final LLMRepository llmRepository;
private final DocumentRepository documentRepository;
private final SummaryRepository summaryRepository;
private final ProblemRepository problemRepository;

public LLMSubmitResult submitTask(LLMSubmitCommand command) {
// TODO: document 개발 코드 올려주시면, getById 로 수정
Document foundDocument =
documentRepository.findById(command.documentId()).orElseThrow(() -> new NotFoundException(""));

command.pages().forEach(pageNumber -> {
UUID taskId = sendRequestToAIServer();
Summary summary = new Summary(foundDocument, pageNumber);
Problem problem = new Problem(foundDocument, pageNumber);

LLM taskRecord = new LLM(taskId, summary, problem);
llmRepository.save(taskRecord);
});

return LLMSubmitResult.of(command.documentId(), LocalDateTime.now());
}

public Integer updateSummaryAndProblem(SummaryAndProblemUpdateCommand command) {
LLM taskRecord = llmRepository.getById(command.taskId());
Summary foundSummary = summaryRepository.getById(taskRecord.getSummary().getId());
Problem foundProblem = problemRepository.getById(taskRecord.getProblem().getId());

taskRecord.completeTask();
foundSummary.updateContent(command.summary());
foundProblem.updateContent(command.problem());

llmRepository.save(taskRecord);
summaryRepository.save(foundSummary);
problemRepository.save(foundProblem);

return command.pageNumber();
}

/**
* 임시 값 반환, 추후 AI 서버에서 작업 단위 UUID 가 반환됨.
*/
private UUID sendRequestToAIServer() {
return UUID.randomUUID();
}
}
10 changes: 10 additions & 0 deletions src/main/java/notai/llm/application/command/LLMSubmitCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package notai.llm.application.command;

import java.util.List;

public record LLMSubmitCommand(
Long documentId,
List<Integer> pages
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package notai.llm.application.command;

import java.util.UUID;

public record SummaryAndProblemUpdateCommand(
UUID taskId,
Integer pageNumber,
String summary,
String problem
) {
}
31 changes: 31 additions & 0 deletions src/main/java/notai/llm/application/result/LLMResultsResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package notai.llm.application.result;

import java.util.List;

public record LLMResultsResult(
Long documentId,
Integer totalPages,
List<LLMResult> results
) {
public static LLMResultsResult of(Long documentId, List<LLMResult> results) {
return new LLMResultsResult(documentId, results.size(), results);
}

public record LLMResult(
Integer pageNumber,
LLMContent content
) {
public static LLMResult of(Integer pageNumber, LLMContent content) {
return new LLMResult(pageNumber, content);
}
}

public record LLMContent(
String summary,
String problem
) {
public static LLMContent of(String summary, String problem) {
return new LLMContent(summary, problem);
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/notai/llm/application/result/LLMStatusResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package notai.llm.application.result;

import notai.llm.domain.TaskStatus;

public record LLMStatusResult(
Long documentId,
TaskStatus overallStatus,
Integer totalPages,
Integer completedPages
) {
public static LLMStatusResult of(Long documentId, TaskStatus overallStatus, Integer totalPages, Integer completedPages) {
return new LLMStatusResult(documentId, overallStatus, totalPages, completedPages);
}
}
12 changes: 12 additions & 0 deletions src/main/java/notai/llm/application/result/LLMSubmitResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package notai.llm.application.result;

import java.time.LocalDateTime;

public record LLMSubmitResult(
Long documentId,
LocalDateTime createdAt
) {
public static LLMSubmitResult of(Long documentId, LocalDateTime createdAt) {
return new LLMSubmitResult(documentId, createdAt);
}
}
Loading

0 comments on commit 31cf7a8

Please sign in to comment.