[PIPELINE] Vision, RAG 파트와 통합 및 예외 처리 보완 이전 Pull Request#17
Conversation
…vice.java, build.gradle에서 Vision 파트 주석 처리
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Summary by CodeRabbit
Walkthrough이 변경 사항은 기존의 파이프라인 컨트롤러에서 TogetherService와 GeminiService를 연속적으로 호출하던 방식을 단일 PromptAssemblyService 호출로 대체하는 리팩터링을 수행합니다. Gemini 관련 DTO, 서비스 인터페이스 및 구현체가 모두 삭제되고, 프롬프트 조립과 생성을 담당하는 새로운 서비스 계층(PromptAssemblyService, PromptGeneratorService)이 도입되었습니다. 프론트엔드 템플릿에서는 입력 폼이 자동완성 드롭다운으로 교체되고, UI 텍스트와 결과 표시 방식이 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PipelineController
participant PromptAssemblyService
participant TogetherService
participant PromptGeneratorService
User->>PipelineController: visionReport, location 입력 제출
PipelineController->>PromptAssemblyService: assemblePrompt(visionReport, location)
PromptAssemblyService->>TogetherService: answer(반려동물 정보 추출 프롬프트)
TogetherService-->>PromptAssemblyService: 추출된 반려동물 정보(JSON)
PromptAssemblyService->>PromptGeneratorService: generatePrompt(반려동물정보, location)
PromptGeneratorService-->>PromptAssemblyService: 최종 프롬프트 문자열
PromptAssemblyService-->>PipelineController: 프롬프트 문자열
PipelineController-->>User: 제작 결과(프롬프트) 반환
Possibly related PRs
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
src/main/java/io/github/petty/pipeline/service/PromptAssemblyService.java (1)
3-5: 명확한 인터페이스지만 예외 처리 개선 가능인터페이스는 간결하고 목적이 명확합니다. 다만, 일반적인
Exception을 던지는 것보다 구체적인 예외 유형을 명시하는 것이 호출하는 쪽에 더 명확한 정보를 제공할 수 있습니다.- String assemblePrompt(String visionReport, String location) throws Exception; + String assemblePrompt(String visionReport, String location) throws IllegalArgumentException, ServiceException;src/main/java/io/github/petty/pipeline/service/PromptGeneratorServiceImpl.java (1)
10-15: JSON 형식에 대한 주석 추가 필요
extractedPetInfoJson이 중괄호({})를 포함하지 않는다는 가정하에 코드가 작성되어 있습니다. 이 가정을 명확히 하는 주석이 있으면 코드의 의도가 더 명확해질 것입니다.@Override public String generatePrompt(String extractedPetInfoJson, String location) { + // extractedPetInfoJson은 중괄호({})가 없는 JSON 내부 콘텐츠만 포함해야 함 + // TogetherService에서 반환된 결과에 이미 중괄호가 제거되어 있음 return String.format(""" { %s "location": "%s" } """, extractedPetInfoJson, location); }src/main/java/io/github/petty/pipeline/service/PromptAssemblyServiceImpl.java (2)
14-18: 프롬프트 문자열 연결 방식 개선현재 코드에서는 문자열 연결을 위해
+연산자를 여러 번 사용하고 있습니다. 이는 가독성을 저하시키고 잠재적인 버그의 원인이 될 수 있습니다. 특히, "no markdown, only JSON"과 "최종 결과에 {}는 제거" 사이에 공백이 없어 의도하지 않은 결과가 나올 수 있습니다.- String extractedPetInfoJson = togetherService.answer( - visionReport + " -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " + - "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " + - "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거" - ); + String extractedPetInfoJson = togetherService.answer(String.format( + "%s -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " + + "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " + + "무게는 kg 단위를 반드시 포함 + no markdown, only JSON + 최종 결과에 {}는 제거", + visionReport + ));
1-22: ServiceException 클래스 추가 필요위의 개선 제안에서
ServiceException클래스를 사용했습니다. 이 클래스가 아직 정의되지 않았다면 추가해야 합니다.package io.github.petty.pipeline.exception; public class ServiceException extends Exception { public ServiceException(String message) { super(message); } public ServiceException(String message, Throwable cause) { super(message, cause); } }이 예외 클래스를 추가하시겠습니까? 아니면 기존에 있는 다른 예외 클래스를 사용하시겠습니까?
src/main/java/io/github/petty/pipeline/controller/PipelineController.java (1)
34-43: 예외 처리 개선 가능성이 있습니다.현재 모든 예외를 catch하고 있지만, 특정 예외 유형에 따라 다른 오류 메시지를 제공하는 것이 사용자 경험을 향상시킬 수 있습니다.
try { String prompt = promptAssemblyService.assemblePrompt(visionReport, location); model.addAttribute("recommendation", prompt); return "pipeline"; -} catch (Exception e) { - log.error("프롬프트 제작 중 오류 발생", e); - model.addAttribute("error", "결과를 받아오지 못했습니다."); +} catch (IllegalArgumentException e) { + log.error("입력 데이터 처리 중 오류 발생", e); + model.addAttribute("error", "입력 정보가 올바르지 않습니다: " + e.getMessage()); + return "pipeline"; +} catch (Exception e) { + log.error("프롬프트 제작 중 오류 발생", e); + model.addAttribute("error", "결과를 받아오지 못했습니다."); return "pipeline"; }src/main/resources/templates/pipeline.html (2)
171-201: 드롭다운 필터링 성능 최적화 가능성이 있습니다.현재 구현은 모든 필터링을 클라이언트 측에서 수행합니다. 지역 목록이 매우 큰 경우 성능에 영향을 줄 수 있습니다. 대규모 데이터셋의 경우 디바운싱 기법을 적용하는 것이 좋습니다.
+ // 입력 디바운싱을 위한 타이머 변수 추가 + let debounceTimer; + locationInput.addEventListener('input', function () { + // 기존 타이머 취소 + clearTimeout(debounceTimer); + + // 타이머 설정: 300ms 후에 필터링 실행 + debounceTimer = setTimeout(() => { const query = this.value.trim().toLowerCase(); dropdownList.innerHTML = ''; if (!query) { dropdownList.style.display = 'none'; return; } const filtered = regions.filter(region => region.toLowerCase().includes(query) ); if (filtered.length === 0) { dropdownList.style.display = 'none'; return; } filtered.forEach(region => { const li = document.createElement('li'); li.textContent = region; li.addEventListener('click', () => { locationInput.value = region; dropdownList.style.display = 'none'; }); dropdownList.appendChild(li); }); dropdownList.style.display = 'block'; + }, 300); });
82-86: 접근성 개선을 위한 ARIA 속성 추가가 필요합니다.현재 자동완성 드롭다운은 WAI-ARIA 표준을 따르지 않아 스크린 리더와 같은 보조 기술을 사용하는 사용자가 어려움을 겪을 수 있습니다.
<label for="locationInput">여행 희망 지역 (예: 강릉, 부평 등)</label> -<div class="dropdown"> - <input type="text" name="location" id="locationInput" placeholder="지역 키워드 검색" autocomplete="off" required> - <ul id="dropdownList" class="dropdown-list"></ul> +<div class="dropdown" role="combobox" aria-haspopup="listbox" aria-expanded="false"> + <input type="text" name="location" id="locationInput" placeholder="지역 키워드 검색" autocomplete="off" required aria-autocomplete="list" aria-controls="dropdownList"> + <ul id="dropdownList" class="dropdown-list" role="listbox"></ul> </div>JavaScript에서도 ARIA 상태를 업데이트하도록 변경:
dropdownList.style.display = 'block'; +document.querySelector('.dropdown').setAttribute('aria-expanded', 'true');dropdownList.style.display = 'none'; +document.querySelector('.dropdown').setAttribute('aria-expanded', 'false');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/main/java/io/github/petty/pipeline/controller/PipelineController.java(3 hunks)src/main/java/io/github/petty/pipeline/model/dto/GeminiRequestDTO.java(0 hunks)src/main/java/io/github/petty/pipeline/model/dto/GeminiResponseDTO.java(0 hunks)src/main/java/io/github/petty/pipeline/service/GeminiService.java(0 hunks)src/main/java/io/github/petty/pipeline/service/GeminiServiceImpl.java(0 hunks)src/main/java/io/github/petty/pipeline/service/PromptAssemblyService.java(1 hunks)src/main/java/io/github/petty/pipeline/service/PromptAssemblyServiceImpl.java(1 hunks)src/main/java/io/github/petty/pipeline/service/PromptGeneratorService.java(1 hunks)src/main/java/io/github/petty/pipeline/service/PromptGeneratorServiceImpl.java(1 hunks)src/main/resources/templates/pipeline.html(1 hunks)
💤 Files with no reviewable changes (4)
- src/main/java/io/github/petty/pipeline/service/GeminiService.java
- src/main/java/io/github/petty/pipeline/model/dto/GeminiRequestDTO.java
- src/main/java/io/github/petty/pipeline/model/dto/GeminiResponseDTO.java
- src/main/java/io/github/petty/pipeline/service/GeminiServiceImpl.java
🔇 Additional comments (14)
src/main/java/io/github/petty/pipeline/service/PromptGeneratorService.java (1)
3-5: 인터페이스 설계가 깔끔합니다.간결하고 목적이 명확한 인터페이스 설계입니다. 매개변수와 반환 타입이 명확히 정의되어 있습니다.
src/main/java/io/github/petty/pipeline/controller/PipelineController.java (5)
3-5: import 문이 리팩토링을 정확히 반영합니다.import 문이 기존 서비스들(GeminiService 등)에서 새로운 PromptAssemblyService와 PromptGeneratorService로 적절하게 변경되었습니다. 여전히 TogetherService는 유지되고 있는 것으로 보입니다.
19-19: 의존성 주입이 올바르게 구현되었습니다.PromptAssemblyService를 필드로 선언하고 @requiredargsconstructor를 통해 자동 주입하는 방식이 Spring 권장 패턴에 일치합니다.
28-31: 한글 주석이 명확하게 처리되었습니다.주석이 한글로 작성되어 있고, 각 매개변수의 용도를 명확하게 설명하고 있습니다.
35-36: 프롬프트 생성 로직이 적절히 서비스로 이동되었습니다.여러 단계의 처리 로직을 PromptAssemblyService로 위임하여 컨트롤러 코드가 간결해졌습니다. 이는 단일 책임 원칙(SRP)을 잘 따르고 있습니다.
40-41: 오류 메시지가 새 기능에 맞게 업데이트되었습니다.오류 로깅 메시지가 "프롬프트 제작 중 오류 발생"으로 변경되어 애플리케이션의 새로운 목적에 맞게 조정되었습니다.
src/main/resources/templates/pipeline.html (8)
51-73: 드롭다운 UI의 CSS 구현이 잘 되어 있습니다.위치 입력을 위한 드롭다운 기능의 CSS가 명확하게 구현되어 있습니다. 요소의 위치 지정, 최대 높이 제한, 스크롤 기능 등이 사용자 경험을 개선하는 데 적절히 사용되었습니다.
77-77: UI 텍스트가 새로운 기능에 맞게 변경되었습니다."여행지 추천 요청"에서 "프롬프트 제작소"로 헤딩이 변경되어 새로운 기능에 맞게 적절히 조정되었습니다.
82-86: 자동완성 드롭다운 UI 구현이 잘 되어 있습니다.기존의 단순 입력 필드가 자동완성 드롭다운으로 대체되어 사용자 경험이 크게 향상되었습니다. 입력 필드와 드롭다운 목록의 구조가 잘 구성되어 있습니다.
88-88: 버튼 텍스트가 새로운 기능에 맞게 변경되었습니다."추천 요청"에서 "제작하기"로 버튼 텍스트가 변경되어 새로운 기능에 맞게 적절히 조정되었습니다.
92-92: 결과 표시 섹션의 제목이 적절히 변경되었습니다."추천 결과"에서 "제작 결과"로 헤딩이 변경되어 새로운 기능에 맞게 적절히 조정되었습니다.
101-169: 한국 행정구역 데이터의 광범위한 목록이 포함되어 있습니다.한국의 행정구역(특별시, 광역시, 도, 시, 군, 구)이 포함된 상세한 목록이 제공되어 있습니다. 사용자에게 정확한 위치 입력을 도울 수 있는 데이터가 잘 구성되어 있습니다.
172-201: 드롭다운 필터링 기능이 효율적으로 구현되어 있습니다.사용자 입력에 따라 지역 목록을 필터링하는 로직이 잘 구현되어 있습니다. 빈 검색어 처리, 일치하는 항목이 없는 경우의 처리 등이 포함되어 있습니다.
203-207: 외부 클릭 처리가 적절히 구현되어 있습니다.드롭다운 영역 외부를 클릭할 때 드롭다운이 닫히는 기능이 구현되어 있어 사용자 경험이 개선되었습니다.
| public String generatePrompt(String extractedPetInfoJson, String location) { | ||
| return String.format(""" | ||
| { | ||
| %s | ||
| "location": "%s" | ||
| } | ||
| """, extractedPetInfoJson, location); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 매개변수 검증 및 예외 처리 필요
현재 구현은 입력 파라미터에 대한 검증이 없습니다. null이나 빈 문자열이 입력될 경우 처리 방법이 없으며, 특수 문자가 포함된 location 값이 JSON에 직접 삽입되면 JSON 파싱 오류가 발생할 수 있습니다.
@Override
public String generatePrompt(String extractedPetInfoJson, String location) {
+ if (extractedPetInfoJson == null || extractedPetInfoJson.trim().isEmpty()) {
+ throw new IllegalArgumentException("추출된 반려동물 정보가 비어 있습니다.");
+ }
+ if (location == null || location.trim().isEmpty()) {
+ throw new IllegalArgumentException("위치 정보가 비어 있습니다.");
+ }
+
+ // 특수 문자 이스케이핑 처리
+ String escapedLocation = location.replace("\"", "\\\"");
+
return String.format("""
{
%s
- "location": "%s"
+ "location": "%s"
}
""", extractedPetInfoJson, escapedLocation);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public String generatePrompt(String extractedPetInfoJson, String location) { | |
| return String.format(""" | |
| { | |
| %s | |
| "location": "%s" | |
| } | |
| """, extractedPetInfoJson, location); | |
| } | |
| @Override | |
| public String generatePrompt(String extractedPetInfoJson, String location) { | |
| if (extractedPetInfoJson == null || extractedPetInfoJson.trim().isEmpty()) { | |
| throw new IllegalArgumentException("추출된 반려동물 정보가 비어 있습니다."); | |
| } | |
| if (location == null || location.trim().isEmpty()) { | |
| throw new IllegalArgumentException("위치 정보가 비어 있습니다."); | |
| } | |
| // 특수 문자 이스케이핑 처리 | |
| String escapedLocation = location.replace("\"", "\\\""); | |
| return String.format(""" | |
| { | |
| %s | |
| "location": "%s" | |
| } | |
| """, extractedPetInfoJson, escapedLocation); | |
| } |
| public String assemblePrompt(String visionReport, String location) throws Exception { | ||
| String extractedPetInfoJson = togetherService.answer( | ||
| visionReport + " -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " + | ||
| "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " + | ||
| "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거" | ||
| ); | ||
|
|
||
| return promptGeneratorService.generatePrompt(extractedPetInfoJson, location); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
프롬프트 하드코딩 및 에러 처리 개선 필요
다음과 같은 개선이 필요합니다:
- 긴 프롬프트 문자열이 코드에 직접 하드코딩되어 있어 유지보수가 어렵습니다.
- 일반 Exception을 던지는 것은 좋은 관행이 아닙니다.
- TogetherService 응답이나 입력 매개변수에 대한 검증이 없습니다.
+ private static final String PET_INFO_EXTRACTION_PROMPT =
+ "%s -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " +
+ "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " +
+ "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거";
- public String assemblePrompt(String visionReport, String location) throws Exception {
+ public String assemblePrompt(String visionReport, String location) throws IllegalArgumentException, ServiceException {
+ // 입력 파라미터 검증
+ if (visionReport == null || visionReport.trim().isEmpty()) {
+ throw new IllegalArgumentException("비전 리포트가 비어 있습니다.");
+ }
+ if (location == null || location.trim().isEmpty()) {
+ throw new IllegalArgumentException("위치 정보가 비어 있습니다.");
+ }
+
+ try {
- String extractedPetInfoJson = togetherService.answer(
- visionReport + " -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " +
- "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " +
- "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거"
- );
+ String extractedPetInfoJson = togetherService.answer(String.format(PET_INFO_EXTRACTION_PROMPT, visionReport));
+
+ // 응답 검증
+ if (extractedPetInfoJson == null || extractedPetInfoJson.trim().isEmpty()) {
+ throw new ServiceException("반려동물 정보 추출에 실패했습니다.");
+ }
return promptGeneratorService.generatePrompt(extractedPetInfoJson, location);
+ } catch (Exception e) {
+ throw new ServiceException("프롬프트 조립 중 오류가 발생했습니다.", e);
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public String assemblePrompt(String visionReport, String location) throws Exception { | |
| String extractedPetInfoJson = togetherService.answer( | |
| visionReport + " -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " + | |
| "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " + | |
| "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거" | |
| ); | |
| return promptGeneratorService.generatePrompt(extractedPetInfoJson, location); | |
| } | |
| // 프롬프트 하드코딩 방지를 위한 상수 분리 | |
| private static final String PET_INFO_EXTRACTION_PROMPT = | |
| "%s -> 이 문장에서 반려동물의 이름(name), 종(species), 무게(weight), 맹수 여부(is_danger(only true or false))를 JSON 형식으로 작성 + " + | |
| "만약 반려동물의 종과 무게를 보았을 때, 입마개가 필요할 것 같다면 맹수 여부를 'true'로 작성 + " + | |
| "무게는 kg 단위를 반드시 포함" + "no markdown, only JSON" + "최종 결과에 {}는 제거"; | |
| public String assemblePrompt(String visionReport, String location) | |
| throws IllegalArgumentException, ServiceException { | |
| // 입력 파라미터 검증 | |
| if (visionReport == null || visionReport.trim().isEmpty()) { | |
| throw new IllegalArgumentException("비전 리포트가 비어 있습니다."); | |
| } | |
| if (location == null || location.trim().isEmpty()) { | |
| throw new IllegalArgumentException("위치 정보가 비어 있습니다."); | |
| } | |
| try { | |
| String extractedPetInfoJson = | |
| togetherService.answer(String.format(PET_INFO_EXTRACTION_PROMPT, visionReport)); | |
| // 응답 검증 | |
| if (extractedPetInfoJson == null || extractedPetInfoJson.trim().isEmpty()) { | |
| throw new ServiceException("반려동물 정보 추출에 실패했습니다."); | |
| } | |
| return promptGeneratorService.generatePrompt(extractedPetInfoJson, location); | |
| } catch (Exception e) { | |
| throw new ServiceException("프롬프트 조립 중 오류가 발생했습니다.", e); | |
| } | |
| } |
리뷰
제안
|
✅ 리뷰
🔧 제안 및 아이디어
|
[PIPELINE] Vision, RAG 파트와 통합 및 예외 처리 보완 이전 Pull Request
📜 PR 내용 요약
⚒️ 작업 및 변경 내용
PromptAssemblyService📚 기타 참고 사항