From 6ba075317a5e16a26c72801e406f8b7d6e31838e Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Thu, 23 Jul 2026 11:04:07 +0900 Subject: [PATCH] =?UTF-8?q?AI=20retry,=20merged,=20classification=20?= =?UTF-8?q?=EC=88=98=EB=8F=99=20=ED=98=B8=EC=B6=9C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/InferenceCommonService.java | 185 ++++++++++++++++++ .../InferenceManualApiController.java | 90 ++++++++- .../dto/InferenceClassificationDto.java | 39 ++++ .../inference/dto/InferenceMergedDto.java | 39 ++++ src/main/resources/application-dev.yml | 2 + src/main/resources/application-local.yml | 6 +- src/main/resources/application-prod.yml | 2 + 7 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceClassificationDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceMergedDto.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java b/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java index 2776a59e..c39ff84f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java @@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; +import com.kamco.cd.kamcoback.inference.dto.InferenceClassificationDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceMergedDto; import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto; import java.util.List; import java.util.Map; @@ -31,6 +33,15 @@ public class InferenceCommonService { @Value("${inference.url}") private String inferenceUrl; + @Value("${inference.merged-url}") + private String mergedUrl; + + @Value("${inference.batch-url}") + private String batchUrl; + + @Value("${inference.classification-url}") + private String classificationUrl; + private final ObjectMapper objectMapper; private final ExternalHttpClient externalHttpClient; @@ -108,4 +119,178 @@ public class InferenceCommonService { throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY); } } + + /** + * MERGED API 호출 batch id를 리턴 + * + * @param dto + * @return + */ + public Long mergedPostProcessAccept(InferenceMergedDto.MergedRequestDto dto) { + + if (dto == null) { + log.warn("not MergedRequestDto dto"); + throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); + } + + log.info(""); + log.info("===================================================================="); + log.info("[SEND MERGED] Merged request dto= {}", dto); + log.info("===================================================================="); + log.info(""); + + // 1) 요청 로그 + try { + log.debug("Inference request dto={}", objectMapper.writeValueAsString(dto)); + } catch (JsonProcessingException e) { + log.warn("Failed to serialize inference dto", e); + } + + // 2) HTTP 호출 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + // 3) MERGED 실행 API 호출 + ExternalCallResult result = + externalHttpClient.callLong(mergedUrl, HttpMethod.POST, dto, headers, String.class); + + if (result.statusCode() < 200 || result.statusCode() >= 300) { + log.error("Merge API failed. status={}, body={}", result.statusCode(), result.body()); + throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY); + } + + // 4) 응답 파싱 + try { + List list = + objectMapper.readValue( + result.body(), new TypeReference>() {}); + + if (list == null || list.isEmpty()) { + throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "Merged response is empty"); + } + + InferenceMergedDto.MergedResponseDto batchIdObj = list.get(0); + + if (batchIdObj.getBatch_id() == null) { + throw new CustomApiException( + "NOT_FOUND", HttpStatus.NOT_FOUND, "batch_id not found in response"); + } + + return Long.valueOf(batchIdObj.getBatch_id().toString()); + + } catch (Exception e) { + log.error("Failed to parse merged response. body={}", result.body(), e); + throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY); + } + } + + /** + * retry API 호출 + * + * @param + * @return + */ + public int retryApiAccept(Long batchId) { + + if (batchId == null) { + log.warn("not batchId"); + throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); + } + + log.info(""); + log.info("===================================================================="); + log.info("[SEND INFERENCE] batchId = {}", batchId); + log.info("===================================================================="); + log.info(""); + + // 1) 요청 로그 + try { + log.debug("batchId={}", objectMapper.writeValueAsString(batchId)); + } catch (JsonProcessingException e) { + log.warn("Failed to serialize batchId", e); + } + + // 2) HTTP 호출 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + // 3) retry API URL + String url = batchUrl + "/" + batchId + "/retry"; + + // 4) 추론 실행 API 호출 + ExternalCallResult result = + externalHttpClient.callLong(url, HttpMethod.POST, null, headers, String.class); + + return result.statusCode(); + } + + /** + * classification API 호출 batchId를 리턴 + * + * @param + * @return + */ + public Long classficationApiAccept(InferenceClassificationDto.ClassificationRequestDto dto) { + + if (dto == null) { + log.warn("not ClassificationRequestDto dto"); + throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); + } + + log.info(""); + log.info("===================================================================="); + log.info("[SEND MERGED] Classification request dto= {}", dto); + log.info("===================================================================="); + log.info(""); + + // 1) 요청 로그 + try { + log.debug("Classification request dto={}", objectMapper.writeValueAsString(dto)); + } catch (JsonProcessingException e) { + log.warn("Failed to serialize inference dto", e); + } + + // 2) HTTP 호출 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + // 3) MERGED 실행 API 호출 + ExternalCallResult result = + externalHttpClient.callLong(classificationUrl, HttpMethod.POST, dto, headers, String.class); + + if (result.statusCode() < 200 || result.statusCode() >= 300) { + log.error( + "Classification API failed. status={}, body={}", result.statusCode(), result.body()); + throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY); + } + + // 4) 응답 파싱 + try { + List list = + objectMapper.readValue( + result.body(), + new TypeReference>() {}); + + if (list == null || list.isEmpty()) { + throw new CustomApiException( + "NOT_FOUND", HttpStatus.NOT_FOUND, "Classification response is empty"); + } + + InferenceClassificationDto.MergedResponseDto batchIdObj = list.get(0); + + if (batchIdObj.getBatch_id() == null) { + throw new CustomApiException( + "NOT_FOUND", HttpStatus.NOT_FOUND, "batch_id not found in response"); + } + + return Long.valueOf(batchIdObj.getBatch_id().toString()); + + } catch (Exception e) { + log.error("Failed to parse Classification response. body={}", result.body(), e); + throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY); + } + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceManualApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceManualApiController.java index 707c922a..12ad9aef 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceManualApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceManualApiController.java @@ -1,8 +1,13 @@ package com.kamco.cd.kamcoback.inference; +import com.kamco.cd.kamcoback.common.inference.service.InferenceCommonService; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceClassificationDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceMergedDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.inference.service.InferenceManualService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -10,9 +15,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; @Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API") @RestController @@ -21,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; public class InferenceManualApiController { private final InferenceManualService inferenceManualService; + private final InferenceCommonService inferenceCommonService; @Operation(summary = "추론 결과로 추론 목록 및 shp 생성", description = "추론 결과로 추론 목록 및 shp 생성") @ApiResponses( @@ -40,4 +45,83 @@ public class InferenceManualApiController { public void saveTesting(List batchIds) { inferenceManualService.saveResultsTesting(batchIds); } + + @Operation(summary = "AI MERGED API 호출", description = "AI MERGED API 호출") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/merged-api") + public ApiResponseDto mergedPostProcessAccept( + @Parameter(description = "batch_ids", example = "2026,2027") @RequestParam + List batchIds, + @Parameter(description = "min_area", example = "10") @RequestParam Integer minArea) { + + InferenceMergedDto.MergedRequestDto request = new InferenceMergedDto.MergedRequestDto(); + request.setBatch_ids(batchIds); + request.setMin_area(Float.valueOf(minArea)); + + // MERGED 실행 api 호출 + Long batchId = inferenceCommonService.mergedPostProcessAccept(request); + return ApiResponseDto.ok(batchId); + } + + @Operation(summary = "AI Classification API 호출", description = "AI Classification API 호출") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/classification-api") + public ApiResponseDto classficationApiAccept( + @Parameter(description = "batch_ids", example = "2026,2027") @RequestParam + List batchIds) { + + InferenceClassificationDto.ClassificationRequestDto request = + new InferenceClassificationDto.ClassificationRequestDto(); + request.setBatch_ids(batchIds); + request.setCls_model_path("/data/ckpt/model/v6-cls-checkpoints/yolov8_6th-6m.pt"); + request.setCls_model_version("testing-re-cls"); + + // Classification 실행 api 호출 + Long batchId = inferenceCommonService.classficationApiAccept(request); + return ApiResponseDto.ok(batchId); + } + + @Operation(summary = "AI Retry API 호출", description = "AI Retry API 호출") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/retry-api") + public ApiResponseDto retryApiAccept( + @Parameter(description = "batch_id", example = "2026") @RequestParam Long batchId) { + + // retryApiAccept 실행 api 호출 + int statusCode = inferenceCommonService.retryApiAccept(batchId); + return ApiResponseDto.ok(statusCode); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceClassificationDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceClassificationDto.java new file mode 100644 index 00000000..5951fa3b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceClassificationDto.java @@ -0,0 +1,39 @@ +package com.kamco.cd.kamcoback.inference.dto; + +import java.time.ZonedDateTime; +import java.util.List; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InferenceClassificationDto { + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + @ToString + public static class ClassificationRequestDto { + private List batch_ids; + private String cls_model_path; + private String cls_model_version; + } + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + @ToString + public static class MergedResponseDto { + private Integer id; + private String scene_id; + private String model_name; + private String status; + private Integer batch_id; + private ZonedDateTime created_at; + private ZonedDateTime started_at; + private ZonedDateTime finished_at; + private String worker_id; + private String error_message; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceMergedDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceMergedDto.java new file mode 100644 index 00000000..e9e40fdb --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceMergedDto.java @@ -0,0 +1,39 @@ +package com.kamco.cd.kamcoback.inference.dto; + +import java.time.ZonedDateTime; +import java.util.List; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InferenceMergedDto { + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + @ToString + public static class MergedRequestDto { + + private List batch_ids; + private Float min_area; + } + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + @ToString + public static class MergedResponseDto { + + private Integer id; + private String scene_id; + private String model_name; + private String status; + private Integer batch_id; + private ZonedDateTime created_at; + private ZonedDateTime started_at; + private ZonedDateTime finished_at; + private String worker_id; + private String error_message; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index da546d59..57d56ab9 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -103,6 +103,8 @@ inference: jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar inference-server-name: server1,server2,server3,server4 output-dir: ${inference.nfs}/model_output/export + merged-url: http://192.168.2.183:8000/merge-postprocess + classification-url: http://192.168.2.183:8000/reclassification gukyuin: #url: http://localhost:8080 diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 294921e8..280bde10 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -75,12 +75,14 @@ file: inference: nfs: C:/Users/gypark/kamco-nfs - url: http://10.100.0.11:8000/jobs - batch-url: http://10.100.0.11:8000/batches + url: http://192.168.2.183:8000/jobs + batch-url: http://192.168.2.183:8000/batches jar-path: jar/shp-exporter.jar jar-path-v2: jar/shp-exporter-v2.jar inference-server-name: server1,server2,server3,server4 output-dir: ${inference.nfs}/model_output/export + merged-url: http://192.168.2.183:8000/merge-postprocess + classification-url: http://192.168.2.183:8000/reclassification gukyuin: #url: http://localhost:8080 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 3581f6ad..33ab2b8b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -99,6 +99,8 @@ inference: jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar inference-server-name: server1,server2,server3,server4 output-dir: ${inference.nfs}/model_output/export + merged-url: http://172.16.4.56:8000/merge-postprocess + classification-url: http://172.16.4.56:8000/reclassification gukyuin: url: http://127.0.0.1:5301