-
Notifications
You must be signed in to change notification settings - Fork 0
[YS-509] feat: 공고 키워드 자동완성 기능 구현 (재배포) #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough실험 게시글의 텍스트에서 키워드를 추출하는 새로운 기능이 도입되었습니다. 이를 위해 도메인, 인프라, 서비스, 프레젠테이션 계층에 걸쳐 유스케이스, 게이트웨이, 매퍼, DTO, 예외, API 엔드포인트, 테스트 등이 추가되었습니다. OpenAI API 연동 및 프롬프트 템플릿 로딩도 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller
participant Service
participant UseCase
participant Gateway
participant OpenAI
Client->>Controller: 키워드 추출 요청(ExtractKeywordRequest)
Controller->>Service: extractExperimentPostKeywords(input)
Service->>UseCase: execute(input)
UseCase->>Gateway: extractKeywords(text)
Gateway->>OpenAI: chatCompletion(OpenAiRequest)
OpenAI-->>Gateway: OpenAiResponse(추출 결과)
Gateway-->>UseCase: ExperimentPostKeyword
UseCase-->>Service: Output(ExperimentPostKeyword)
Service-->>Controller: Output(ExperimentPostKeyword)
Controller-->>Client: ExtractKeywordResponse
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. 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 (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (5)
infrastructure/src/main/kotlin/com/dobby/config/properties/OpenAiProperties.kt (1)
6-14: API Key 미설정 검증 로직 추가 제안운영 환경에서
openai.api.key값이 비어 있으면 즉시 장애로 이어집니다.@Validated와@field:NotBlank를 활용해 부트 초기화 시점에 검증하도록 권장합니다.import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.stereotype.Component +import org.springframework.validation.annotation.Validated +import jakarta.validation.constraints.NotBlank @Component -@ConfigurationProperties(prefix = "openai") +@ConfigurationProperties(prefix = "openai") +@Validated data class OpenAiProperties( var api: Api = Api() ) { data class Api( - var key: String = "" + @field:NotBlank + var key: String = "" ) }infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplate.kt (1)
3-9: 타입 안정성 및 네이밍 컨벤션 고려 필요
output_format을Map<String, Any>로 두면 후속 로직에서 값 타입 캐스팅이 잦아집니다. 자료구조를 별도 DTO로 명시하면 타입 안정성이 향상됩니다.- 속성명이 snake_case라서 Kotlin 관례와 다릅니다. JSON 매핑 목적이라면
@JsonProperty("output_format")등으로 매핑하고 프로퍼티 자체는 camelCase로 두는 방식을 고려해 보세요.큰 변경이 필요 없다면 그대로 유지해도 무방하나, 장기적인 유지보수를 위해 검토를 권장합니다.
domain/src/main/kotlin/com/dobby/model/experiment/keyword/ApplyMethodKeyword.kt (1)
3-9: 🔧 Boolean 기본값을 지정하여 생성 편의성 향상
isFormUrl/isPhoneNum가 필수(true/false) 값이지만 기본값이 없어서 생성 시 매번 명시해야 합니다. 기본값을false로 주면 DTO ↔︎ 도메인 매핑 코드가 간결해집니다.-data class ApplyMethodKeyword( - val content: String?, - val isFormUrl: Boolean, - val formUrl: String?, - val isPhoneNum: Boolean, - val phoneNum: String? +data class ApplyMethodKeyword( + val content: String? = null, + val isFormUrl: Boolean = false, + val formUrl: String? = null, + val isPhoneNum: Boolean = false, + val phoneNum: String? = null )infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/TargetGroupDto.kt (1)
4-7: 🤔 DTO 레이어에서도 Enum 타입을 사용하는 편이 직렬화-역직렬화 오류를 줄입니다
genderType을String으로 두면 오타나 예상치 못한 값이 들어왔을 때 컴파일 타임 검증이 불가능합니다. DTO 에도GenderType?을 사용하고, 필요 시@JsonProperty나@JsonValue로 직렬화 형식을 맞추는 방안을 고려해 주세요.infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt (1)
37-41: 선택적 개선 사항: 모델 및 온도 파라미터 설정 가능하게 만들기현재
gpt-4o모델과0.2온도가 하드코딩되어 있습니다. 향후 유연성을 위해 설정 파일로 외부화하는 것을 고려해보세요.Properties 클래스에 다음과 같이 추가할 수 있습니다:
// OpenAiProperties에 추가 val model: String = "gpt-4o" val temperature: Double = 0.2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt(3 hunks)application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt(1 hunks)application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt(1 hunks)domain/src/main/kotlin/com/dobby/exception/DobbyException.kt(1 hunks)domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt(1 hunks)domain/src/main/kotlin/com/dobby/model/experiment/keyword/ApplyMethodKeyword.kt(1 hunks)domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt(1 hunks)domain/src/main/kotlin/com/dobby/model/experiment/keyword/TargetGroupKeyword.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/config/properties/OpenAiProperties.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplate.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplateLoader.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ApplyMethodDto.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ExperimentPostKeywordDto.kt(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/TargetGroupDto.kt(1 hunks)infrastructure/src/main/resources/prompts/keyword_extraction_prompt.json(1 hunks)presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt(3 hunks)presentation/src/main/kotlin/com/dobby/api/dto/request/OpenAiRequest.kt(1 hunks)presentation/src/main/kotlin/com/dobby/api/dto/request/experiment/ExtractKeywordRequest.kt(1 hunks)presentation/src/main/kotlin/com/dobby/api/dto/response/OpenAiResponse.kt(1 hunks)presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt(1 hunks)presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt(4 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt:10-14
Timestamp: 2025-07-17T08:06:13.596Z
Learning: Ji-soo708의 팀은 현재 OpenAI 공식 API만 사용할 예정이므로, FeignClient URL을 외부화하지 않고 하드코딩하는 것을 선호한다. 팀의 요구사항에 따라 단순함을 우선시하는 접근 방식을 취한다.
infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt:10-14
Timestamp: 2025-07-17T08:06:13.596Z
Learning: Ji-soo708의 팀은 현재 OpenAI 공식 API만 사용할 예정이므로, FeignClient URL을 외부화하지 않고 하드코딩하는 것을 선호한다. 팀의 요구사항에 따라 단순함을 우선시하는 접근 방식을 취한다.
domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (21)
domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt (1)
5-7: 도메인 계층 인터페이스 설계 적합 확인
현재 인터페이스는 단순 계약만 정의하며 외부 의존성이 없고, 반환 타입도 명확합니다. 특이사항 없습니다.infrastructure/src/main/resources/prompts/keyword_extraction_prompt.json (1)
16-35: 💡 도메인 Enum 이름과 JSON Enum 값의 일치 여부를 반드시 확인하세요
matchType,genderType,timeRequired등에 사용된 문자열 리터럴이 실제 코드상의 Enum 상수와 정확히 일치하지 않으면 매핑 시IllegalArgumentException이 발생합니다.
특히"timeRequired"값의LESS_30M/ABOUT_30M… 목록과, 도메인 계층에 정의된 시간 구간 Enum 이름이 1:1 대응하는지 검증이 필요합니다.presentation/src/main/kotlin/com/dobby/api/dto/request/experiment/ExtractKeywordRequest.kt (1)
6-10: LGTM! 잘 구조화된 DTO입니다.텍스트 필드에 대한 적절한 유효성 검증(@notblank)과 Swagger 문서화(@Schema)가 포함되어 있습니다. 예시 텍스트도 도메인에 적합합니다.
application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt (3)
11-11: 적절한 의존성 추가입니다.새로운 키워드 추출 기능을 위한 UseCase 의존성이 올바르게 추가되었습니다.
45-45: 생성자 주입이 올바르게 구현되었습니다.새로운 UseCase가 private 필드로 적절히 주입되었습니다.
170-172: 서비스 메서드가 적절히 구현되었습니다.UseCase로의 단순한 위임 패턴을 따르고 있으며, 외부 API 호출이므로 @transactional을 사용하지 않은 것이 적절합니다.
domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt (1)
6-13: 도메인 모델이 잘 설계되었습니다.AI 키워드 추출 특성상 모든 필드가 nullable로 설정된 것이 적절하며, 도메인 enum과 관련 키워드 객체를 활용한 구조가 좋습니다.
infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt (1)
10-18: Feign 클라이언트가 올바르게 구성되었습니다.OpenAI API URL 하드코딩은 팀의 선호에 따른 단순함을 우선시하는 접근 방식으로 적절합니다. 커스텀 설정 클래스를 통한 인증 처리도 좋은 구조입니다.
infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ApplyMethodDto.kt (1)
1-10: 깔끔한 DTO 구조입니다.프로퍼티들이 적절한 기본값과 함께 잘 정의되어 있고, 키워드 추출 결과를 담기에 적합한 구조입니다.
infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplateLoader.kt (1)
6-19: 리소스 로딩 로직이 잘 구현되었습니다.Jackson을 사용한 JSON 역직렬화와 적절한 에러 처리, 리소스 관리가 잘 되어 있습니다.
use함수를 통한 자동 리소스 해제도 적절합니다.presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt (1)
222-235: 일관된 패턴으로 잘 구현된 엔드포인트입니다.기존 컨트롤러의 패턴을 일관되게 따르고 있으며, 적절한 인증 설정과 문서화가 되어 있습니다.
presentation/src/main/kotlin/com/dobby/api/dto/request/OpenAiRequest.kt (1)
1-13: OpenAI API 요청 구조가 적절히 모델링되었습니다.OpenAI의 채팅 완성 API 스펙에 맞는 구조로 잘 정의되어 있습니다. 중첩된 Message 클래스도 적절합니다.
application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (1)
11-34: 유스케이스 테스트가 적절히 작성되었습니다.BDD 스타일로 명확하게 구조화되어 있고, 게이트웨이 모킹과 검증이 올바르게 구현되어 있습니다. 팀의 테스트 패턴을 잘 따르고 있습니다.
presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)
524-534: LGTM! 키워드 추출 매핑 함수가 올바르게 구현되었습니다.매핑 함수들이 기존 패턴과 일관성 있게 구현되어 있고, presentation 계층과 application 계층 간의 명확한 분리를 제공합니다.
infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ExperimentPostKeywordDto.kt (1)
5-14: LGTM! AI 응답 파싱에 적합한 DTO 설계입니다.Jackson 어노테이션을 통한 JSON 매핑이 올바르게 설정되어 있고, 모든 필드가 nullable로 설계되어 AI 키워드 추출 결과의 불확실성을 적절히 반영하고 있습니다.
application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (1)
7-17: LGTM! UseCase 패턴이 올바르게 구현되었습니다.Gateway로의 단순한 위임을 통해 관심사의 분리가 잘 이루어져 있고, Input/Output 클래스 구조가 명확합니다.
infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt (2)
17-60: LGTM! AI 키워드 추출의 특성을 잘 반영한 매핑 로직입니다.Enum 변환 실패 시 기본값으로 처리하는 로직이 적절합니다. 이전 학습 내용에 따르면, AI 텍스트 추출 과정에서 enum 변환 실패는 정상적인 상황이므로 현재의 예외 처리 방식이 올바릅니다.
62-87: LGTM! Domain에서 DTO로의 역변환이 올바르게 구현되었습니다.Null 처리와 enum의 name 속성 사용이 적절하며, 양방향 매핑이 일관성 있게 구현되어 있습니다.
infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt (3)
30-60: LGTM! OpenAI API 통합이 견고하게 구현되었습니다.예외 처리가 포괄적이며, FeignException과 일반 예외를 적절히 구분하여 처리하고 있습니다. JSON 응답 정리 로직도 AI 응답의 특성(마크다운 코드 블록)을 잘 고려했습니다.
62-68: LGTM! JSON 문자열 이스케이프 로직이 적절합니다.주요 JSON 특수 문자들을 올바르게 처리하고 있습니다.
70-78: LGTM! AI 응답 정리 로직이 효과적입니다.마크다운 코드 블록과 백틱 제거 로직이 AI 응답의 특성을 잘 반영하고 있습니다.
| /** | ||
| * OpenAI API call specific exceptions | ||
| */ | ||
| data class CustomOpenAiCallException(override val message: String, override val cause: Throwable? = null) : ServerException("AI0001", message, cause) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CustomOpenAiCallException에서 컴파일 오류 발생 가능성
Throwable.message는 String? 타입이므로, 이를 override val message: String(nonnull)로 재정의하면 null-ability가 좁아져 컴파일이 실패합니다. 또한 예외를 data class로 선언할 필요성이 낮아 equals/hashCode 생성 등 부가 오버헤드만 증가합니다.
-data class CustomOpenAiCallException(override val message: String, override val cause: Throwable? = null) : ServerException("AI0001", message, cause)
+class CustomOpenAiCallException(
+ message: String,
+ cause: Throwable? = null
+) : ServerException("AI0001", message, cause)🤖 Prompt for AI Agents
In domain/src/main/kotlin/com/dobby/exception/DobbyException.kt around lines 81
to 84, the CustomOpenAiCallException class overrides the nullable
Throwable.message property with a non-null String, causing a compile error due
to nullability mismatch. Also, declaring it as a data class is unnecessary and
adds overhead. To fix this, change CustomOpenAiCallException to a regular class
(not data class) and override message with a nullable String type matching
Throwable.message.
| data class TargetGroupKeyword( | ||
| var startAge: Int?, | ||
| var endAge: Int?, | ||
| var genderType: GenderType?, | ||
| var otherCondition: String? | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
var 대신 val 사용 고려
도메인 모델은 생성 이후 변하지 않는 값이 대부분이므로 가변 프로퍼티(var)를 사용하면 예기치 않은 상태 변경이 가능해집니다. 불변 객체로 유지해도 기능 요구사항에는 영향이 없으니 다음과 같이 수정하는 것을 권장합니다.
-data class TargetGroupKeyword(
- var startAge: Int?,
- var endAge: Int?,
- var genderType: GenderType?,
- var otherCondition: String?
+data class TargetGroupKeyword(
+ val startAge: Int? = null,
+ val endAge: Int? = null,
+ val genderType: GenderType? = null,
+ val otherCondition: String? = null
)🤖 Prompt for AI Agents
In
domain/src/main/kotlin/com/dobby/model/experiment/keyword/TargetGroupKeyword.kt
around lines 5 to 10, the properties are declared with 'var', making them
mutable. To ensure domain object immutability and prevent unintended state
changes, change all 'var' declarations to 'val' since the values do not need to
change after creation.
| class OpenAiFeignConfig( | ||
| private val openAiProperties: OpenAiProperties | ||
| ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@configuration 어노테이션이 누락되었습니다.
Spring 설정 클래스로 인식되도록 @configuration 어노테이션을 추가해야 합니다.
다음과 같이 수정하세요:
+@Configuration
class OpenAiFeignConfig(
private val openAiProperties: OpenAiProperties
) {📝 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.
| class OpenAiFeignConfig( | |
| private val openAiProperties: OpenAiProperties | |
| ) { | |
| @Configuration | |
| class OpenAiFeignConfig( | |
| private val openAiProperties: OpenAiProperties | |
| ) { | |
| // existing bean definitions, e.g. RequestInterceptor bean | |
| } |
🤖 Prompt for AI Agents
In infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt around
lines 7 to 9, the class OpenAiFeignConfig is missing the @Configuration
annotation. Add the @Configuration annotation above the class declaration to
ensure Spring recognizes it as a configuration class.
| return RequestInterceptor { template -> | ||
| template.header("Authorization", "Bearer ${openAiProperties.api.key}") | ||
| template.header("Content-Type", "application/json") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
API 키에 대한 null 안전성 검증을 추가하세요.
API 키가 null이거나 비어있을 경우 잘못된 Authorization 헤더가 생성될 수 있습니다.
다음과 같이 개선하는 것을 권장합니다:
return RequestInterceptor { template ->
- template.header("Authorization", "Bearer ${openAiProperties.api.key}")
+ val apiKey = openAiProperties.api.key
+ require(!apiKey.isNullOrBlank()) { "OpenAI API key must not be null or blank" }
+ template.header("Authorization", "Bearer $apiKey")
template.header("Content-Type", "application/json")
}📝 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.
| return RequestInterceptor { template -> | |
| template.header("Authorization", "Bearer ${openAiProperties.api.key}") | |
| template.header("Content-Type", "application/json") | |
| } | |
| return RequestInterceptor { template -> | |
| val apiKey = openAiProperties.api.key | |
| require(!apiKey.isNullOrBlank()) { "OpenAI API key must not be null or blank" } | |
| template.header("Authorization", "Bearer $apiKey") | |
| template.header("Content-Type", "application/json") | |
| } |
🤖 Prompt for AI Agents
In infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt around
lines 12 to 15, the code sets the Authorization header using the API key without
checking if the key is null or empty, which can lead to invalid headers. Add a
null and empty check for openAiProperties.api.key before setting the
Authorization header, and only set the header if the key is valid to ensure null
safety and prevent malformed requests.
| data class ExtractKeywordResponse( | ||
| @Schema(description = "추출된 키워드 정보") | ||
| val experimentPostKeyword: ExperimentPostKeyword |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🚥 API 응답에서 도메인 객체 직접 노출은 계층 간 의존도를 높입니다
ExperimentPostKeyword 가 변경될 때마다 API 스펙이 깨질 위험이 있습니다. 별도 Response DTO 로 변환해 노출 계층을 분리하는 것을 권장합니다.
data class ExtractKeywordResponse(
@Schema(description = "추출된 키워드 정보")
val keyword: ExtractKeywordDto // ← 프레젠테이션 전용 DTO
)이렇게 하면 도메인 변경이 외부 계약(API)에 영향을 주지 않습니다.
🤖 Prompt for AI Agents
In
presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt
around lines 6 to 8, the API response directly exposes the domain object
ExperimentPostKeyword, which increases coupling between layers and risks
breaking the API contract if the domain changes. To fix this, create a separate
presentation-layer DTO (e.g., ExtractKeywordDto) that maps the necessary fields
from ExperimentPostKeyword, and update ExtractKeywordResponse to use this new
DTO instead of the domain object, thereby decoupling the API response from
domain changes.
| data class OpenAiResponse( | ||
| val choices: List<Choice> | ||
| ) { | ||
| data class Choice( | ||
| val message: Message | ||
| ) | ||
|
|
||
| data class Message( | ||
| val role: String, | ||
| val content: String | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
미정의 필드가 포함된 OpenAI 응답 파싱 실패 우려
OpenAI Chat Completion API 응답에는 id, usage, 등 다수의 필드가 존재합니다. 현재 클래스에 정의되지 않은 속성이 전달되면 Jackson 기본 설정에서 FAIL_ON_UNKNOWN_PROPERTIES=true 인 경우 역직렬화가 실패할 수 있습니다. 안전하게 무시하도록 어노테이션을 추가해 주세요.
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+
-data class OpenAiResponse(
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class OpenAiResponse(
val choices: List<Choice>
) {
+ @JsonIgnoreProperties(ignoreUnknown = true)
data class Choice(
val message: Message
)
+ @JsonIgnoreProperties(ignoreUnknown = true)
data class Message(
val role: String,
val content: String
)
}📝 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.
| data class OpenAiResponse( | |
| val choices: List<Choice> | |
| ) { | |
| data class Choice( | |
| val message: Message | |
| ) | |
| data class Message( | |
| val role: String, | |
| val content: String | |
| ) | |
| } | |
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties | |
| @JsonIgnoreProperties(ignoreUnknown = true) | |
| data class OpenAiResponse( | |
| val choices: List<Choice> | |
| ) { | |
| @JsonIgnoreProperties(ignoreUnknown = true) | |
| data class Choice( | |
| val message: Message | |
| ) | |
| @JsonIgnoreProperties(ignoreUnknown = true) | |
| data class Message( | |
| val role: String, | |
| val content: String | |
| ) | |
| } |
🤖 Prompt for AI Agents
In presentation/src/main/kotlin/com/dobby/api/dto/response/OpenAiResponse.kt
around lines 3 to 14, the OpenAiResponse data class lacks handling for unknown
JSON properties, which can cause deserialization failures if the API response
includes fields like id or usage not defined in the class. Add the Jackson
annotation @JsonIgnoreProperties(ignoreUnknown = true) to the OpenAiResponse
class to safely ignore any undefined fields during deserialization.
* feat: add ExperimentKeyword for prompt * feat: add ExperimentKeywordExtractionGateway with OpenAI integration * feat: configure OpenAI API client with Feign * feat: add prompt json file for keyword extraction * fix: fix typo * feat: add extractExperimentPostKeywords api * fix: add default values to Dto fields for null safety * style: apply ktlint formatting * test: add ExtractExperimentPostKeywordsUseCase test code * refactor: reuse ObjectMapper instance to improve efficiency * feat: add CustomOpenAiCallException for OpenAI API call errors * refactor: move ExperimentPostKeyword DTO to `/prompt/dto` for better clarity * style: add ktlint formatting * refactor: replace `@Service` with `@Component` * feat: add field length limits to extraction prompt * fix: restrict OpenAI Feign interceptor to avoid affecting global OAuth clients
* feat: add ExperimentKeyword for prompt * feat: add ExperimentKeywordExtractionGateway with OpenAI integration * feat: configure OpenAI API client with Feign * feat: add prompt json file for keyword extraction * fix: fix typo * feat: add extractExperimentPostKeywords api * fix: add default values to Dto fields for null safety * style: apply ktlint formatting * test: add ExtractExperimentPostKeywordsUseCase test code * refactor: reuse ObjectMapper instance to improve efficiency * feat: add CustomOpenAiCallException for OpenAI API call errors * refactor: move ExperimentPostKeyword DTO to `/prompt/dto` for better clarity * style: add ktlint formatting * refactor: replace `@Service` with `@Component` * feat: add field length limits to extraction prompt * fix: restrict OpenAI Feign interceptor to avoid affecting global OAuth clients
💡 작업 내용
✅ 셀프 체크리스트
🙋🏻 확인해주세요
🔗 Jira 티켓
https://yappsocks.atlassian.net/browse/YS-509
Summary by CodeRabbit
신규 기능
버그 수정
문서화
테스트