AI retry, merged, classification 수동 호출 API 추가

This commit is contained in:
2026-07-23 11:04:07 +09:00
parent 4ac3bcc175
commit 6ba075317a
7 changed files with 358 additions and 5 deletions

View File

@@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; 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 com.kamco.cd.kamcoback.inference.dto.InferenceSendDto;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -31,6 +33,15 @@ public class InferenceCommonService {
@Value("${inference.url}") @Value("${inference.url}")
private String inferenceUrl; 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 ObjectMapper objectMapper;
private final ExternalHttpClient externalHttpClient; private final ExternalHttpClient externalHttpClient;
@@ -108,4 +119,178 @@ public class InferenceCommonService {
throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY); 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<String> 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<InferenceMergedDto.MergedResponseDto> list =
objectMapper.readValue(
result.body(), new TypeReference<List<InferenceMergedDto.MergedResponseDto>>() {});
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<String> 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<String> 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<InferenceClassificationDto.MergedResponseDto> list =
objectMapper.readValue(
result.body(),
new TypeReference<List<InferenceClassificationDto.MergedResponseDto>>() {});
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);
}
}
} }

View File

@@ -1,8 +1,13 @@
package com.kamco.cd.kamcoback.inference; 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.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.inference.service.InferenceManualService; import com.kamco.cd.kamcoback.inference.service.InferenceManualService;
import io.swagger.v3.oas.annotations.Operation; 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.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; 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 io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API") @Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API")
@RestController @RestController
@@ -21,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
public class InferenceManualApiController { public class InferenceManualApiController {
private final InferenceManualService inferenceManualService; private final InferenceManualService inferenceManualService;
private final InferenceCommonService inferenceCommonService;
@Operation(summary = "추론 결과로 추론 목록 및 shp 생성", description = "추론 결과로 추론 목록 및 shp 생성") @Operation(summary = "추론 결과로 추론 목록 및 shp 생성", description = "추론 결과로 추론 목록 및 shp 생성")
@ApiResponses( @ApiResponses(
@@ -40,4 +45,83 @@ public class InferenceManualApiController {
public void saveTesting(List<Long> batchIds) { public void saveTesting(List<Long> batchIds) {
inferenceManualService.saveResultsTesting(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<Long> mergedPostProcessAccept(
@Parameter(description = "batch_ids", example = "2026,2027") @RequestParam
List<Integer> 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<Long> classficationApiAccept(
@Parameter(description = "batch_ids", example = "2026,2027") @RequestParam
List<Integer> 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<Integer> retryApiAccept(
@Parameter(description = "batch_id", example = "2026") @RequestParam Long batchId) {
// retryApiAccept 실행 api 호출
int statusCode = inferenceCommonService.retryApiAccept(batchId);
return ApiResponseDto.ok(statusCode);
}
} }

View File

@@ -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<Integer> 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;
}
}

View File

@@ -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<Integer> 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;
}
}

View File

@@ -103,6 +103,8 @@ inference:
jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export 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: gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080

View File

@@ -75,12 +75,14 @@ file:
inference: inference:
nfs: C:/Users/gypark/kamco-nfs nfs: C:/Users/gypark/kamco-nfs
url: http://10.100.0.11:8000/jobs url: http://192.168.2.183:8000/jobs
batch-url: http://10.100.0.11:8000/batches batch-url: http://192.168.2.183:8000/batches
jar-path: jar/shp-exporter.jar jar-path: jar/shp-exporter.jar
jar-path-v2: jar/shp-exporter-v2.jar jar-path-v2: jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export 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: gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080

View File

@@ -99,6 +99,8 @@ inference:
jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export 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: gukyuin:
url: http://127.0.0.1:5301 url: http://127.0.0.1:5301