From ff1c9d74bc07bd6d243f8ae459e0daaed0a1c557 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Mon, 19 Aug 2024 17:17:24 +0900 Subject: [PATCH 1/4] Feat : .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 68a4238..e7bc1a0 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ foodData.csv Untitled-1.py /src/main/resources/secret.yml /menu-data +*.txt From 0f8f5cd2237769730ebc3aee51c6541f19a03e41 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Mon, 19 Aug 2024 17:17:45 +0900 Subject: [PATCH 2/4] Feat : Gpt Service --- .../recommend/application/ChatService.java | 7 +- .../recommend/config/AzureConfig.java | 91 +++++++++++++++++ .../recommend/repository/GptManagerImpl.java | 99 ++++++++++++------- 3 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/mju/capstone/recommend/config/AzureConfig.java diff --git a/src/main/java/com/mju/capstone/recommend/application/ChatService.java b/src/main/java/com/mju/capstone/recommend/application/ChatService.java index d7ad780..0d18f2f 100644 --- a/src/main/java/com/mju/capstone/recommend/application/ChatService.java +++ b/src/main/java/com/mju/capstone/recommend/application/ChatService.java @@ -35,8 +35,7 @@ public List getChatResponse(MenuRecommendRequest menuRec log.info(prompt); List response = gptManager.sendOpenAIRequest(prompt); - -// List recommends = response.menus(); + log.info("response size : " + response.size()); return response.stream().map(this::getTotalRecommendResponseByFoodName) .collect(Collectors.toUnmodifiableList()); @@ -55,8 +54,7 @@ private String createNutritionPrompt(MenuRecommendRequest request, prefPrompt = "사용자는 " + tasteType + " " + menuCountry + " " + ingredient + " 음식을 선호해."; } String result = String.format( - "사용자가 %s으로 %s 해 먹을 식단을 업로드한 파일 내에서 추천해줘. 탄수화물 %dg, 단백질 %dg, 지방 %dg 을 섭취해야 해." + prefPrompt - + " 응답 형식은 다른 말 없이 무조건 다음과 같아야 해 : JSON [String , int]", + "사용자가 %s으로 %s로 먹어야해. 탄수화물 %dg, 단백질 %dg, 지방 %dg 을 섭취해야 해." + prefPrompt, request.mealTime(),request.cookOrDelivery(),supposedNutrition.carbohydrate(), supposedNutrition.protein(),supposedNutrition.fat() ); @@ -65,6 +63,7 @@ private String createNutritionPrompt(MenuRecommendRequest request, } public TotalRecommendResponse getTotalRecommendResponseByFoodName(Menu response) { + log.info(response.name()); Food food = staticFoodService.findFoodByName(response.name()); int amount = response.amount(); int kcal = (int) ((double) food.getKcal() * amount / 100); diff --git a/src/main/java/com/mju/capstone/recommend/config/AzureConfig.java b/src/main/java/com/mju/capstone/recommend/config/AzureConfig.java new file mode 100644 index 0000000..caa8c81 --- /dev/null +++ b/src/main/java/com/mju/capstone/recommend/config/AzureConfig.java @@ -0,0 +1,91 @@ +package com.mju.capstone.recommend.config; + + +import com.azure.ai.openai.assistants.AssistantsClient; +import com.azure.ai.openai.assistants.AssistantsClientBuilder; +import com.azure.ai.openai.assistants.AssistantsServiceVersion; +import com.azure.ai.openai.assistants.models.Assistant; +import com.azure.ai.openai.assistants.models.AssistantCreationOptions; +import com.azure.ai.openai.assistants.models.CreateFileSearchToolResourceOptions; +import com.azure.ai.openai.assistants.models.CreateFileSearchToolResourceVectorStoreOptions; +import com.azure.ai.openai.assistants.models.CreateFileSearchToolResourceVectorStoreOptionsList; +import com.azure.ai.openai.assistants.models.CreateToolResourcesOptions; +import com.azure.ai.openai.assistants.models.FileDetails; +import com.azure.ai.openai.assistants.models.FilePurpose; +import com.azure.ai.openai.assistants.models.FileSearchToolDefinition; +import com.azure.ai.openai.assistants.models.OpenAIFile; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.util.BinaryData; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class AzureConfig { + + @Value("${azure.credential}") + private String credentialKey; + @Value("${azure.endpoint}") + private String endpoint; + @Value("${azure.key}") + private String key; + @Value("${azure.model}") + private String model; + + @Bean + AssistantsClient assistantsClient() { + return new AssistantsClientBuilder() + .credential(new AzureKeyCredential(key)) + .serviceVersion(AssistantsServiceVersion.getLatest()) + .endpoint(endpoint) + .buildClient(); + } + + @Bean + public Assistant customAssistant(AssistantsClient client) throws IOException { + + Path filePath = Paths.get("src/main/resources/menu_final.txt"); + BinaryData fileData = BinaryData.fromFile(filePath); + FileDetails fileDetails = new FileDetails(fileData, "menu_final.txt"); + + + OpenAIFile openAIFile = client.uploadFile(fileDetails, FilePurpose.ASSISTANTS); + + String instructions = loadInstructionsFromFile("instruction2.txt"); + log.info("Application Started with Instructions: {}", instructions); + + CreateToolResourcesOptions createToolResourcesOptions = new CreateToolResourcesOptions(); + createToolResourcesOptions.setFileSearch( + new CreateFileSearchToolResourceOptions( + new CreateFileSearchToolResourceVectorStoreOptionsList( + Arrays.asList(new CreateFileSearchToolResourceVectorStoreOptions(Arrays.asList(openAIFile.getId())))))); + + return client.createAssistant( + new AssistantCreationOptions(model) + .setName("영양사") + .setInstructions(instructions) + .setTools(Arrays.asList(new FileSearchToolDefinition())) + .setToolResources(createToolResourcesOptions) + ); + } + + private String loadInstructionsFromFile(String filePath) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + Objects.requireNonNull(this.getClass().getClassLoader().getResourceAsStream(filePath)), StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } +} diff --git a/src/main/java/com/mju/capstone/recommend/repository/GptManagerImpl.java b/src/main/java/com/mju/capstone/recommend/repository/GptManagerImpl.java index 135e97d..2413284 100644 --- a/src/main/java/com/mju/capstone/recommend/repository/GptManagerImpl.java +++ b/src/main/java/com/mju/capstone/recommend/repository/GptManagerImpl.java @@ -1,61 +1,92 @@ package com.mju.capstone.recommend.repository; -import static org.springframework.http.MediaType.APPLICATION_JSON; +import static com.azure.ai.openai.assistants.models.MessageRole.USER; -import com.mju.capstone.global.exception.BusinessException; -import com.mju.capstone.global.response.message.ErrorMessage; +import com.azure.ai.openai.assistants.AssistantsClient; +import com.azure.ai.openai.assistants.models.Assistant; +import com.azure.ai.openai.assistants.models.AssistantThread; +import com.azure.ai.openai.assistants.models.AssistantThreadCreationOptions; +import com.azure.ai.openai.assistants.models.CreateRunOptions; +import com.azure.ai.openai.assistants.models.MessageContent; +import com.azure.ai.openai.assistants.models.MessageTextContent; +import com.azure.ai.openai.assistants.models.PageableList; +import com.azure.ai.openai.assistants.models.RunStatus; +import com.azure.ai.openai.assistants.models.ThreadMessage; +import com.azure.ai.openai.assistants.models.ThreadMessageOptions; +import com.azure.ai.openai.assistants.models.ThreadRun; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mju.capstone.recommend.domain.GptManager; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import com.mju.capstone.recommend.dto.response.Menu; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Repository; -import org.springframework.web.client.RestTemplate; @Repository @PropertySource("classpath:application.yml") +@RequiredArgsConstructor @Slf4j public class GptManagerImpl implements GptManager { - private final RestTemplate restTemplate; + private final AssistantsClient client; + private final Assistant assistant; + + public List sendOpenAIRequest(String messageContent) { + log.info("Processing message: {}", messageContent); + + AssistantThread thread = createAssistantThread(); + sendMessageToThread(thread.getId(), messageContent); - @Value("${python-server.url}") - private String server_url; + List result; - public GptManagerImpl(RestTemplate restTemplate) { - this.restTemplate = restTemplate; + try { + result = getGptResponse(thread.getId()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return result; } - public List sendOpenAIRequest(String request) { + private AssistantThread createAssistantThread() { + return client.createThread(new AssistantThreadCreationOptions()); + } - String url = server_url; - Map requestBody = new HashMap<>(); - requestBody.put("content", request); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(APPLICATION_JSON); + private void sendMessageToThread(String threadId, String messageContent) { + client.createMessage(threadId, new ThreadMessageOptions(USER, messageContent)); + } - HttpEntity> requestHttpEntity = new HttpEntity<>(requestBody, headers); + private List getGptResponse(String threadId) throws InterruptedException { + ThreadRun run = client.createRun(threadId, new CreateRunOptions(assistant.getId())); -// ResponseModel response= restTemplate.postForObject(url, requestHttpEntity, ResponseModel.class); + do { + run = client.getRun(run.getThreadId(), run.getId()); + Thread.sleep(500); + } while (run.getStatus() == RunStatus.QUEUED || run.getStatus() == RunStatus.IN_PROGRESS); - ResponseEntity> responseEntity = - restTemplate.exchange(url,HttpMethod.POST,requestHttpEntity,new ParameterizedTypeReference>() {}); + return extractMessagesFromResponse(client.listMessages(run.getThreadId())); + } - log.info(responseEntity.getBody().toString()); - List recommendResponse = responseEntity.getBody(); + private List extractMessagesFromResponse(PageableList messages) { + List result = new ArrayList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + ThreadMessage threadMessage = messages.getData().getFirst(); - if (recommendResponse.isEmpty() || recommendResponse == null) { - throw new BusinessException(ErrorMessage.RECOMMEND_NOT_FOUND); + for (MessageContent messageContent : threadMessage.getContent()) { + String jsonResponse = ((MessageTextContent) messageContent).getText().getValue(); + log.info("Message content: {}", jsonResponse); + try { + jsonResponse = jsonResponse.replaceAll("```json", "").trim(); + List menu = objectMapper.readValue(jsonResponse, new TypeReference<>() { + }); + result.addAll(menu); + } catch (Exception e) { + e.printStackTrace(); + } } - return recommendResponse; + log.info("result: {}" ,result.toString()); + return result; } } From 630b1e801d1a5c7fed4ad3e19e61fb4ea7fd074b Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Mon, 19 Aug 2024 17:17:52 +0900 Subject: [PATCH 3/4] Feat : build.gradle --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index f71feb3..ab1f8e1 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,10 @@ dependencies { //WebFlux // implementation 'org.springframework:spring-webflux:6.1.6' + + //azure + implementation group: 'com.azure', name: 'azure-ai-openai-assistants', version: '1.0.0-beta.3' + } tasks.named('test') { From fb6ca65cba306c11c737fb6c4e315c96d1a5c040 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Mon, 19 Aug 2024 17:26:15 +0900 Subject: [PATCH 4/4] Feat : github actions --- .github/workflows/deployment.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index f2dba06..28386c4 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -45,6 +45,16 @@ jobs: echo "${{ secrets.PROPERTIES }}" > ./application.yml shell: bash + - name: make model foundation + if: contains(github.ref, 'develop') + run: | + cd ./src/main/resources + touch ./instruction.txt + echo "${{ secrets.ENV_INSTRUCTION }}" > ./instruction.txt + touch ./menu_final.txt + echo "${{ secrets.ENV_MENU_FINAL }}" > ./menu_final.txt + shell: bash + # gradle build 위한 permission - name: Grant execute permission for gradlew run: chmod +x ./gradlew