diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestCommand.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestCommand.java index f29d0fd..fe757e0 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestCommand.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestCommand.java @@ -14,5 +14,4 @@ public class JobPostingIngestCommand { private byte[] imageBytes; private String imageContentType; private CompanySize companySize; - private Integer candidateLimit; } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestMultipartRequest.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestMultipartRequest.java index ea7d828..107f5b2 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestMultipartRequest.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingIngestMultipartRequest.java @@ -4,6 +4,6 @@ import org.springframework.web.multipart.MultipartFile; public record JobPostingIngestMultipartRequest(String rawText, String sourceUrl, MultipartFile image, - CompanySize companySize, Integer candidateLimit) { + CompanySize companySize) { } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiAsyncService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiAsyncService.java new file mode 100644 index 0000000..5e7f7b5 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiAsyncService.java @@ -0,0 +1,36 @@ +package com.jobdri.jobdri_api.domain.jobposting.service; + +import com.jobdri.jobdri_api.domain.company.entity.Company; +import com.jobdri.jobdri_api.domain.jobposting.dto.request.JobPostingMockGenerateRequest; +import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingMockGenerateResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Service +@RequiredArgsConstructor +public class JobPostingAiAsyncService { + + private final JobPostingAiService jobPostingAiService; + private final MockQuestionCacheService mockQuestionCacheService; + + @Async("llmAsyncExecutor") + public CompletableFuture generateMockJobPosting( + JobPostingMockGenerateRequest request, + Company company + ) { + return CompletableFuture.completedFuture( + jobPostingAiService.generateMockJobPosting(request, company) + ); + } + + @Async("llmAsyncExecutor") + public CompletableFuture> getRecommendedQuestions(JobPostingMockGenerateRequest request) { + return CompletableFuture.completedFuture( + mockQuestionCacheService.getRecommendedQuestions(request) + ); + } +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAsyncFacadeService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAsyncFacadeService.java index 23aa3f6..002cc60 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAsyncFacadeService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAsyncFacadeService.java @@ -55,7 +55,6 @@ private JobPostingIngestCommand snapshot(User user, JobPostingIngestMultipartReq .imageBytes(readBytes(request.image())) .imageContentType(readContentType(request.image())) .companySize(request.companySize()) - .candidateLimit(request.candidateLimit()) .build(); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestService.java index f093e8f..a5cfa1a 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestService.java @@ -24,7 +24,7 @@ @RequiredArgsConstructor public class JobPostingIngestService { - private static final int DEFAULT_CANDIDATE_LIMIT = 10; + private static final int FIXED_CANDIDATE_LIMIT = 5; @Value("${job-posting.ingest.classification-confidence-threshold:0.65}") private double classificationConfidenceThreshold; @@ -40,7 +40,6 @@ public JobPostingIngestResponse ingestAndCreate(User user, JobPostingIngestMulti .rawText(request.rawText()) .sourceUrl(request.sourceUrl()) .companySize(request.companySize()) - .candidateLimit(request.candidateLimit()) .build(); return ingestAndCreate(command); } @@ -54,9 +53,8 @@ public JobPostingIngestResponse ingestAndCreate(JobPostingIngestCommand command) command.getSourceUrl() ); - int candidateLimit = command.getCandidateLimit() == null ? DEFAULT_CANDIDATE_LIMIT : command.getCandidateLimit(); List candidates = - jobPostingClassificationService.findCandidates(extracted, candidateLimit); + jobPostingClassificationService.findCandidates(extracted, FIXED_CANDIDATE_LIMIT); if (candidates.isEmpty()) { throw new GeneralException( diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/MockJobPostingGenerationService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/MockJobPostingGenerationService.java index 7c603be..0bd85ac 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/MockJobPostingGenerationService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/MockJobPostingGenerationService.java @@ -10,14 +10,15 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.concurrent.CompletableFuture; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MockJobPostingGenerationService { private final CompanyRepository companyRepository; - private final JobPostingAiService jobPostingAiService; - private final MockQuestionCacheService mockQuestionCacheService; + private final JobPostingAiAsyncService jobPostingAiAsyncService; public JobPostingMockGenerateResponse generate(JobPostingMockGenerateRequest request) { Company company = companyRepository.findById(request.companyId()) @@ -26,7 +27,14 @@ public JobPostingMockGenerateResponse generate(JobPostingMockGenerateRequest req "해당 회사를 찾을 수 없습니다. companyId=" + request.companyId() )); - JobPostingMockGenerateResponse generatedPosting = jobPostingAiService.generateMockJobPosting(request, company); + CompletableFuture generatedPostingFuture = + jobPostingAiAsyncService.generateMockJobPosting(request, company); + CompletableFuture> recommendedQuestionsFuture = + jobPostingAiAsyncService.getRecommendedQuestions(request); + + CompletableFuture.allOf(generatedPostingFuture, recommendedQuestionsFuture).join(); + JobPostingMockGenerateResponse generatedPosting = generatedPostingFuture.join(); + return new JobPostingMockGenerateResponse( generatedPosting.companyName(), generatedPosting.jobTitle(), @@ -34,7 +42,7 @@ public JobPostingMockGenerateResponse generate(JobPostingMockGenerateRequest req generatedPosting.requirement(), generatedPosting.preferred(), generatedPosting.summary(), - mockQuestionCacheService.getRecommendedQuestions(request) + recommendedQuestionsFuture.join() ); } } diff --git a/src/main/java/com/jobdri/jobdri_api/global/config/AsyncConfig.java b/src/main/java/com/jobdri/jobdri_api/global/config/AsyncConfig.java index 9104e36..fce29e5 100644 --- a/src/main/java/com/jobdri/jobdri_api/global/config/AsyncConfig.java +++ b/src/main/java/com/jobdri/jobdri_api/global/config/AsyncConfig.java @@ -45,4 +45,22 @@ public ThreadPoolTaskExecutor mailAsyncExecutor( executor.initialize(); return executor; } + + @Bean(name = "llmAsyncExecutor") + public ThreadPoolTaskExecutor llmAsyncExecutor( + @Value("${async.llm.core-pool-size:2}") int corePoolSize, + @Value("${async.llm.max-pool-size:4}") int maxPoolSize, + @Value("${async.llm.queue-capacity:20}") int queueCapacity + ) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("llm-async-"); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setAllowCoreThreadTimeOut(true); + executor.setWaitForTasksToCompleteOnShutdown(false); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + executor.initialize(); + return executor; + } }