Skip to content

Commit

Permalink
Merge pull request #13 from KHTML-Hexagonal/develop
Browse files Browse the repository at this point in the history
[FEAT]: Material Gpt Api 구현
  • Loading branch information
sejineer authored Aug 13, 2024
2 parents d339b48 + d322d9d commit 621ca3e
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import org.khtml.hexagonal.domain.building.dto.BuildingUpdate;
import org.khtml.hexagonal.domain.building.dto.ImageRequest;
import org.khtml.hexagonal.domain.building.dto.MaterialInfo;
import org.khtml.hexagonal.domain.building.dto.MaterialResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
Expand All @@ -18,10 +20,9 @@
import java.util.List;
import java.util.Map;

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class GptService {
@Component
public class GptManager {

@Value("${openai.api.url}")
private String openAiApiUrl;
Expand All @@ -30,41 +31,52 @@ public class GptService {
private String openAiApiKey;

public BuildingUpdate analyzeBuilding(List<String> urls) throws JsonProcessingException {
BuildingUpdate buildingUpdate;
ImageRequest imageRequest = new ImageRequest();
List<ImageRequest.Content> contentList = new ArrayList<>();
ImageRequest imageRequest = getImageRequest(urls);
return analyzeHouseImages(imageRequest);
}

imageRequest.setRole("user");
public MaterialResult analyzeMaterial(List<String> urls) throws JsonProcessingException {
ImageRequest imageRequest = getImageRequest(urls);
Map<String, Object> result = analyzeMaterialImages(imageRequest);

for (String url : urls) {
ImageRequest.Content content = new ImageRequest.Content();
ImageRequest.ImageUrl imageUrl = new ImageRequest.ImageUrl();
String material = null;
String usage = null;

imageUrl.setUrl(url);
content.setType("image_url");
content.setImage_url(imageUrl);
contentList.add(content);
List<Map<String, Object>> choices = (List<Map<String, Object>>) result.get("choices");
if (choices != null && !choices.isEmpty()) {
Map<String, Object> firstChoice = choices.get(0);
Map<String, Object> message = (Map<String, Object>) firstChoice.get("message");
if (message != null) {
String content = (String) message.get("content");

// content 값을 JSON으로 파싱
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonData = objectMapper.readValue(content, new TypeReference<Map<String, Object>>() {
});

// 필요한 데이터 추출
material = (String) jsonData.get("material");
usage = (String) jsonData.get("usage");
}
}

imageRequest.setContent(contentList);
return analyzeHouseImages(imageRequest);
return new MaterialResult(material, usage);
}

public BuildingUpdate analyzeHouseImages(ImageRequest imageRequest) throws JsonProcessingException {

// 시스템 메시지
String systemMessage = """
너는 오래된 건물의 상태를 평가하는 챗봇 AI다. 제공된 사진을 분석하여 건물의 상태를 진단하고, 결과를 한글로 작성된 JSON 형식으로 출력해야 한다.
이 JSON은 아래 예시와 같이 각 필드가 특정 영어 변수에 매핑되도록 구성되어야 한다.
건물 구조 분석 (structureReason):
구조: 건물의 구조가 전통적인지 현대적인지 판단한다.
이유: 판단의 이유를 설명한다.
건축 요소 평가 (roof, walls, windowsAndDoors):
지붕 형태 (roof):
재료: 지붕에 사용된 재료를 확인한다.
상태: 지붕의 상태를 평가한다.
Expand All @@ -75,27 +87,27 @@ public BuildingUpdate analyzeHouseImages(ImageRequest imageRequest) throws JsonP
재료: 창문과 문에 사용된 재료를 확인한다.
상태: 창문과 문의 상태를 평가한다.
건물 상태 평가 (overallCondition):
평가: 건물의 전반적인 상태를 구체적으로 평가한다.
이유: 해당 평가의 이유를 제시한다.
상세 점수화 (detailedScores):
균열 여부 (cracks): 균열의 존재 여부와 심각성을 100점 만점으로 점수화한다.
누수 여부 (leaks): 누수의 존재 여부와 심각성을 100점 만점으로 점수화한다.
부식 여부 (corrosion): 부식의 정도를 100점 만점으로 점수화한다.
노후화 정도 (aging): 건물의 노후화를 100점 만점으로 점수화한다.
총점수 (totalScore): 위의 점수를 합산하여 반올림한 총점을 계산한다.
보수 필요성 판단 (repairNeeds):
조명: 사진 속 조명이 형광등이라면 LED로 교체가 필요한지, 이미 LED라면 교체가 불필요한지 판단한다.
창호 보강, 도배, 장판 교체: 창호 보강, 도배, 장판 교체의 필요 여부를 판단한다.
판단하기 어려운 부분, 예를 들어 실내 사진인데 지붕 재료, 지붕 상태 등을 판단해야 하는 경우에는 해당 부분을 ""으로 처리한다.
repairlist의 경우 반드시 ,로 구분한다. repairlist가 없는 경우에는 ""으로 처리한다.
예시 JSON은 다음과 같다.
{
"structureReason": "구조 평가 이유",
"roofMaterial": "지붕 재료",
Expand All @@ -113,10 +125,10 @@ public BuildingUpdate analyzeHouseImages(ImageRequest imageRequest) throws JsonP
"totalScore": 20,
"repairList": "LED 교체, 창호 보강, 도배, 장판 교체"
}
""";

// OpenAI API에 보낼 요청 데이터 작성
Expand Down Expand Up @@ -182,7 +194,8 @@ public BuildingUpdate analyzeHouseImages(ImageRequest imageRequest) throws JsonP

// JSON 데이터를 Map으로 파싱
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonData = objectMapper.readValue(jsonContent, new TypeReference<Map<String, Object>>() {});
Map<String, Object> jsonData = objectMapper.readValue(jsonContent, new TypeReference<Map<String, Object>>() {
});

// BuildingUpdate DTO로 데이터 매핑
BuildingUpdate buildingUpdate = new BuildingUpdate();
Expand Down Expand Up @@ -211,27 +224,27 @@ public BuildingUpdate analyzeHouseImages(ImageRequest imageRequest) throws JsonP
return buildingUpdate;
}

public ResponseEntity<Map<String, Object>> analyzeMaterialImages(ImageRequest imageRequest) throws JsonProcessingException {
public Map<String, Object> analyzeMaterialImages(ImageRequest imageRequest) throws JsonProcessingException {

// 시스템 메시지
String systemMessage = """
너는 사진을 받으면 해당 사진에 나오는 부자재의 종류들과 그 사용법들을 반환하는 챗봇 AI다.
제공된 사진을 분석하여 부자재의 종류와 사용법을 양식에 맞게 작성된 JSON 형식으로 출력해야 한다.
반드시 양식에 맞게 작성해야 하며, 다른 방식으로의 응답은 절대 허용하지 않는다.
mateial과 usage는 여러개가 올 수 있지만 리스트 형식으로는 '절대' 출력하면 안된다.
mateiral과 usage가 여러 가지 이상으로 판단될 시, 반드시 ,로만 구분하여 한 개의 string으로 출력해야 한다.
예시 JSON은 다음과 같다.
{
"material" : "나무 판자, 쇠 파이프"
"usage" : "벽 만들기, 천장 고치기"
}
""";
너는 사진을 받으면 해당 사진에 나오는 부자재의 종류들과 그 사용법들을 반환하는 챗봇 AI다.
제공된 사진을 분석하여 부자재의 종류와 사용법을 양식에 맞게 작성된 JSON 형식으로 출력해야 한다.
반드시 양식에 맞게 작성해야 하며, 다른 방식으로의 응답은 절대 허용하지 않는다.
mateial과 usage는 여러개가 올 수 있지만 리스트 형식으로는 '절대' 출력하면 안된다.
mateiral과 usage가 여러 가지 이상으로 판단될 시, 반드시 ,로만 구분하여 한 개의 string으로 출력해야 한다.
예시 JSON은 다음과 같다.
{
"material" : "나무 판자, 쇠 파이프",
"usage" : "벽 만들기, 천장 고치기"
}
""";

// OpenAI API에 보낼 요청 데이터 작성
Map<String, Object> requestBody = new HashMap<>();
Expand Down Expand Up @@ -292,17 +305,38 @@ public ResponseEntity<Map<String, Object>> analyzeMaterialImages(ImageRequest im
String content = (String) ((Map<String, Object>) ((Map<String, Object>) ((List<Object>) responseBody.get("choices")).get(0)).get("message")).get("content");

// "```json"과 "```"을 제거하여 실제 JSON 데이터만 추출
String jsonContent = content.replaceAll("```json\\n|```", "").trim();
String jsonContent = content.replaceAll("(?s)```json\\s*|```", "").trim();

// JSON 데이터를 Map으로 파싱
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonData = objectMapper.readValue(jsonContent, new TypeReference<Map<String, Object>>() {});
Map<String, Object> jsonData = objectMapper.readValue(jsonContent, new TypeReference<Map<String, Object>>() {
});
// 필요한 데이터 사용
MaterialInfo materialInfo = new MaterialInfo((String) jsonData.get("material"), (String) jsonData.get("usage"));
System.out.println(materialInfo);

// 리턴값 반환
return ResponseEntity.ok(responseBody);
return responseBody;
}

private static ImageRequest getImageRequest(List<String> urls) {
ImageRequest imageRequest = new ImageRequest();
List<ImageRequest.Content> contentList = new ArrayList<>();

imageRequest.setRole("user");

for (String url : urls) {
ImageRequest.Content content = new ImageRequest.Content();
ImageRequest.ImageUrl imageUrl = new ImageRequest.ImageUrl();

imageUrl.setUrl(url);
content.setType("image_url");
content.setImage_url(imageUrl);
contentList.add(content);
}

imageRequest.setContent(contentList);
return imageRequest;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void updateBuildingDescription(String buildingId, Long userId, String des
throw new IllegalArgumentException(ErrorType.DEFAULT_ERROR.getMessage());
}

building.setDescription(description);
building.setBuildingDescription(description);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.khtml.hexagonal.domain.building.controller;

import lombok.RequiredArgsConstructor;
import org.khtml.hexagonal.domain.ai.application.GptService;
import org.khtml.hexagonal.domain.ai.application.GptManager;
import org.khtml.hexagonal.domain.building.dto.*;
import org.khtml.hexagonal.domain.auth.JwtValidator;
import org.khtml.hexagonal.domain.building.application.BuildingService;
Expand All @@ -20,7 +20,7 @@ public class BuildingController {

private final BuildingService buildingService;
private final JwtValidator jwtValidator;
private final GptService gptService;
private final GptManager gptManager;

@GetMapping("/{building-id}")
public ApiResponse<BuildingDetailResponse> getBuildingDetail(
Expand All @@ -38,7 +38,7 @@ public ApiResponse<?> registerBuilding(
User requestUser = jwtValidator.getUserFromToken(token);
List<String> urls = buildingService.registerBuilding(buildingId, requestUser, multipartFiles);

BuildingUpdate buildingUpdate = gptService.analyzeBuilding(urls);
BuildingUpdate buildingUpdate = gptManager.analyzeBuilding(urls);
buildingService.updateAnalyzedBuilding(buildingId, buildingUpdate);

return ApiResponse.success(buildingUpdate);
Expand All @@ -56,5 +56,4 @@ public ApiResponse<?> registerBuilding(
return ApiResponse.success();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static BuildingDetailResponse toResponse(Building building) {
return new BuildingDetailResponse(
building.getGisBuildingId(),
building.getLegalDistrictName() + " " + building.getLandLotNumber(),
building.getDescription(),
building.getBuildingDescription(),
building.getUser() == null ? null : building.getUser().getPhoneNumber(),
building.getCrackScore(),
building.getLeakScore(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.khtml.hexagonal.domain.building.dto;

public record MaterialResult(
String material,
String usage
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,14 @@ public class Building {
@Column(name = "repair_list")
private String repairList;

@Column(name = "description")
private String description;
@Column(name = "building_description")
private String buildingDescription;

@Column(name = "material_description")
private String materialDescription;

@Column(name = "material_usage")
private String materialUsage;

@Column(name = "is_analyzed")
private Boolean isAnalyzed = false;
Expand All @@ -117,6 +123,10 @@ public void updateUser(User user) {
this.user = user;
}

public void updateMaterialUsage(String materialUsage) {
this.materialUsage = materialUsage;
}

public void updateAnalyzedData(BuildingUpdate buildingUpdate) {
this.structureReason = buildingUpdate.getStructureReason();
this.roofMaterial = buildingUpdate.getRoofMaterial();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import org.khtml.hexagonal.domain.auth.JwtValidator;
import org.khtml.hexagonal.domain.building.dto.BuildingDescriptionRequest;
import org.khtml.hexagonal.domain.user.User;
import org.khtml.hexagonal.global.support.response.ApiResponse;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -30,4 +31,16 @@ public ApiResponse<?> registerBuilding(
return ApiResponse.success();
}

@PostMapping("/{building-id}/register/description")
public ApiResponse<?> registerBuilding(
@RequestHeader("Authorization") String token,
@PathVariable(name = "building-id") String buildingId,
@RequestBody BuildingDescriptionRequest buildingDescriptionRequest
) throws IOException {
User requestUser = jwtValidator.getUserFromToken(token);
materialService.updateMaterialDescription(buildingId, requestUser.getId(), buildingDescriptionRequest.description());

return ApiResponse.success();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface MaterialRepository extends JpaRepository<Material, Long> {

Boolean existsByName(String name);

}
Loading

0 comments on commit 621ca3e

Please sign in to comment.