-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Implement full AI-based personalized vacation planning (generation/storage/retrieval) with MCP integration #70
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
|
Caution Review failedThe pull request is closed. """ Walkthrough이 변경은 AI 기반 휴가 생성 기능의 데이터 모델, API, 서비스, 저장소 계층을 전면적으로 리팩토링합니다. 요청 및 응답 모델이 날짜 범위 기반으로 변경되었고, 휴가 생성 및 조회 흐름이 도메인 엔티티와 캐시 기반으로 재구성되었으며, JPA 기반의 영속성 계층이 새로 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as RecommendController
participant Service as RecommendServiceImpl
participant Repo as AiVacationRepository
Client->>API: POST /generateVacation (userId, request)
API->>Service: generateVacation(userId, request)
Service->>Repo: save(AiVacation)
API-->>Client: "휴가 생성 성공" 메시지
Client->>API: GET /getVacation (userId, request)
API->>Service: getVacation(userId)
Service->>Repo: findByUserIdAndSearchDate(userId, today)
Repo-->>Service: AiVacation
Service-->>API: AiVacationApiResponse
API-->>Client: ApiResponse<AiVacationApiResponse>
sequenceDiagram
participant MCPClient
participant ChatbotController
participant ChatbotService
participant AI
MCPClient->>ChatbotController: POST /generate-vacation (startDate, endDate)
ChatbotController->>ChatbotService: generateVacationContent(request)
loop 날짜 chunk별
ChatbotService->>AI: detailedPlanPrompt
AI-->>ChatbotService: itinerary chunk
end
ChatbotService->>ChatbotController: AiVacationContent
ChatbotController->>ChatbotService: summarizeTitle(content)
ChatbotService->>AI: titlePrompt
AI-->>ChatbotService: title
ChatbotService-->>ChatbotController: AiVacationTitle
ChatbotController-->>MCPClient: AiVacationResponse (title, content, startDate, endDate)
Possibly related PRs
Suggested labels
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (5)
✨ Finishing Touches
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: 10
🧹 Nitpick comments (5)
noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationJpaRepository.kt (1)
6-9:findAllByUserId에 페이징 옵션을 고려해 주세요유저가 생성한 휴가가 많아질 경우, 전체 리스트를 한 번에 조회하면 메모리·응답시간이 늘어날 수 있습니다.
Pageable파라미터를 추가하거나Slice/Page형태로 반환하도록 오버로드 메서드를 제공하면 확장성이 좋아집니다.noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendClient.kt (1)
82-91: 로그 태그 오타:getSandwich→generateVacation남아 있는 이전 명칭 때문에 추적이 어렵습니다. 아래와 같이 교체를 권장합니다.
- log.error("[getSandwich] 예기치 못한 예외, empty 반환.", e) + log.error("[generateVacation] 예기치 못한 예외, null 반환.", e)noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/response/AiVacationApiResponse.kt (1)
6-12:Serializable구현 고려API DTO 가 캐시/세션 등에 저장될 가능성이 있다면
: java.io.Serializable구현을 권장합니다.
추가 비용이 없으므로 미리 선언해 두면 활용도가 높습니다.-data class AiVacationApiResponse( +data class AiVacationApiResponse((implements Serializable 코드 추가)
noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationWriter.kt (1)
11-13: 메소드 네이밍과 반환값에 대한 고려사항
register메소드명이 도메인 의미를 잘 표현하고 있습니다. 다만save작업의 결과나 생성된 엔티티를 반환하지 않는 설계가 의도된 것인지 확인해보세요.향후 저장된 엔티티 정보가 필요할 경우를 고려하여 반환값을 추가할 수 있습니다:
- fun register(aiVacation: AiVacation) { - aiVacationRepository.save(aiVacation) + fun register(aiVacation: AiVacation): AiVacation { + return aiVacationRepository.save(aiVacation) }noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacation.kt (1)
6-15: 데이터 클래스로 변환 고려이 클래스는 데이터를 보관하는 역할을 하므로
data class로 선언하면equals(),hashCode(),toString(),copy()메서드를 자동으로 제공받을 수 있습니다.-class AiVacation( +data class AiVacation( val id: String, val title: String, val content: String, val startDate: LocalDate, val endDate: LocalDate, val searchDate: LocalDate, val userId: String, val iconStyle: IconStyle, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendApi.kt(2 hunks)noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendClient.kt(2 hunks)noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/model/AiGenerateVacation.kt(2 hunks)noweekend-clients/client-mcp/src/main/resources/client-mcp.yml(1 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/RecommendController.kt(2 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/docs/RecommendControllerDocs.kt(4 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/response/AiGenerateVacationApiResponse.kt(0 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/response/AiVacationApiResponse.kt(1 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/GenerateVacation.kt(0 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendService.kt(2 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendServiceImpl.kt(6 hunks)noweekend-core/core-api/src/main/kotlin/noweekend/core/support/error/ErrorType.kt(1 hunks)noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacation.kt(1 hunks)noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationReader.kt(1 hunks)noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationRepository.kt(1 hunks)noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationWriter.kt(1 hunks)noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/ChatbotController.kt(2 hunks)noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/Prompt.kt(2 hunks)noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/request/AiGenerateVacation.kt(2 hunks)noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/service/ChatbotService.kt(4 hunks)noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationCoreRepository.kt(1 hunks)noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationEntity.kt(1 hunks)noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationJpaRepository.kt(1 hunks)
💤 Files with no reviewable changes (2)
- noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/GenerateVacation.kt
- noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/response/AiGenerateVacationApiResponse.kt
🔇 Additional comments (27)
noweekend-core/core-api/src/main/kotlin/noweekend/core/support/error/ErrorType.kt (1)
29-29: 에러 코드·메시지 중복 관리 필요
VACATION_NOT_FOUND(E404) 추가로 동일 코드(E404)를 갖는 항목이 4개 이상이 되었습니다.
프론트엔드·로깅 측에서 구분이 어려우므로, 코드 혹은 메시지에 세분화가 필요합니다.noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendClient.kt (1)
5-5: 미사용 import 여부 확인
AiVacationResponse는 아래generateVacation에서만 사용되므로 import 유지가 타당하지만, IDE 자동 최적화 시 삭제되지 않도록 확인 바랍니다.noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendApi.kt (1)
46-46: null 허용 여부 일관성 검토Feign 인터페이스는 non-nullable
AiVacationResponse를 반환하지만,
RecommendClient.generateVacation에서는 nullable 로 감쌉니다.
실제 MCP 서버가 204/404 등을 보낼 가능성이 있다면 인터페이스도AiVacationResponse?로 맞추는 것이 안전합니다.noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationWriter.kt (1)
6-14: 트랜잭션 처리와 구조가 올바르게 구현되었습니다.도메인 계층의 쓰기 담당 컴포넌트로서 적절한 설계입니다. 클래스 레벨의
@Transactional사용과 생성자 주입 방식이 올바릅니다.noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationRepository.kt (2)
5-9: 저장소 인터페이스 설계가 잘 구현되었습니다.도메인 엔티티를 중심으로 한 깔끔한 저장소 인터페이스입니다. 메소드명이 명확하고 반환 타입 선택이 적절합니다.
7-7: 검색 메소드의 nullable 반환 타입이 적절합니다.
findByUserIdAndSearchDate가 nullableAiVacation?을 반환하는 것은 데이터가 없을 수 있는 상황을 명확히 표현합니다.noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendService.kt (1)
14-16: CQRS 패턴을 적용한 인터페이스 설계 개선
generateVacation과getVacation의 분리는 명령과 조회의 책임을 명확히 구분하는 좋은 설계입니다. 비동기 처리 패턴을 지원하여 확장성과 사용자 경험을 개선할 수 있습니다.noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacationReader.kt (2)
7-18: 읽기 전용 트랜잭션 처리가 올바르게 구현되었습니다.
@Transactional(readOnly = true)사용으로 읽기 성능 최적화를 적용했고, 저장소 계층으로의 깔끔한 위임 패턴을 따르고 있습니다.AiVacationWriter와 일관된 설계입니다.
12-17: 메소드 구현이 간결하고 명확합니다.두 메소드 모두 저장소 계층으로의 단순 위임으로 구현되어 있어 계층 간 책임 분리가 명확합니다.
noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/docs/RecommendControllerDocs.kt (2)
10-10: 응답 타입 업데이트가 적절합니다.
AiVacationApiResponse로의 변경이 도메인 모델 변경사항과 일치합니다.
415-441: 상세한 예시 응답이 API 이해도를 높입니다.새로운 필드들(
startDate,endDate,title,content)이 포함된 풍부한 예시 응답이 API 사용자에게 도움이 될 것입니다.noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/model/AiGenerateVacation.kt (2)
5-23: 요청 모델의 날짜 범위 기반 리팩토링이 올바르게 구현되었습니다.
days기반에서startDate와endDate로의 변경은 더 명확한 휴가 기간 정의를 제공합니다.birthDate와upcomingHolidays제거는 모델을 단순화하고 핵심 기능에 집중하게 합니다.
25-30: 응답 모델의 명명과 구조 개선이 적절합니다.
AiGenerateVacationResponse에서AiVacationResponse로의 리네이밍은 더 직관적이며,startDate와endDate필드 추가로 명시적인 휴가 기간 정보를 제공합니다.noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationEntity.kt (3)
14-23: 고유 제약 조건이 올바르게 설정되었습니다.
(user_id, search_date)조합에 대한 고유 제약 조건은 사용자별 특정 검색 날짜에 대한 중복 생성을 방지하여 데이터 무결성을 보장합니다. 이는 캐시 기반 접근 방식에 적합합니다.
32-33: TEXT 타입 사용이 적절합니다.
content필드에columnDefinition = "TEXT"를 사용하여 긴 휴가 계획 내용을 저장할 수 있도록 한 것은 올바른 선택입니다.
52-73: 도메인-엔티티 변환 함수가 완전하고 정확합니다.양방향 변환 함수가 모든 필드를 적절히 매핑하고 있으며, 확장 함수 사용으로 코드의 가독성과 유지보수성이 향상되었습니다.
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/ChatbotController.kt (1)
39-47: 2단계 휴가 생성 프로세스가 잘 구현되었습니다.콘텐츠 생성과 제목 요약을 분리한 접근 방식은 관심사 분리 원칙을 잘 따르며, 요청의 날짜 정보를 응답에 포함시켜 명확성을 높였습니다.
noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/RecommendController.kt (1)
60-61: 비동기 휴가 생성 패턴이 적절하게 구현되었습니다.휴가 생성을 비동기로 처리하고 간단한 성공 메시지를 반환하는 방식은 사용자 경험을 개선하며, 긴 처리 시간에 대한 대응이 잘 되어 있습니다.
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/request/AiGenerateVacation.kt (2)
5-23: 요청 모델이 날짜 범위 기반으로 효과적으로 단순화되었습니다.
days기반에서 명시적인startDate와endDate로의 변경은 더 직관적이며, 불필요한 복잡성을 제거하여 모델을 개선했습니다.
25-38: 응답 클래스 구조가 잘 설계되었습니다.
AiVacationContent,AiVacationTitle,AiVacationResponse로 분리한 구조는 각각의 역할이 명확하며, 최종 응답에 날짜 범위 정보를 포함시켜 완전성을 높였습니다.noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacation.kt (1)
40-45: LGTM!
IconStyleenum이 깔끔하게 정의되어 있습니다.noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationCoreRepository.kt (2)
8-11: LGTM!Repository 패턴이 깔끔하게 구현되었습니다.
13-23: 확장 함수 정의 확인 완료
noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/vacation/AiVacationEntity.kt파일에서
fun AiVacation.toEntity()(라인 53–62)fun AiVacationEntity.toDomain()(라인 64–73)확장 함수가 모두 정상 정의되어 있음을 확인했습니다.
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/Prompt.kt (1)
120-191: 프롬프트 구조 개선 확인한국어 프롬프트로의 전환과 구조적 개선이 잘 이루어졌습니다. 특히 다음 사항들이 개선되었습니다:
- 명확한 출력 형식 지정 (하루당 정확히 6개 불릿 포인트)
- 날씨 정보 사용 금지 명시
- Perplexity 사용 의무화
- 재사용 금지 로직 추가
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/service/ChatbotService.kt (2)
217-233: 제목 요약 로직 검증제목 요약 구현이 깔끔합니다. 15자 제한도 적절히 처리되었습니다.
208-215: 정규식 패턴 확인사용된 아이템 추출 로직이 적절하게 구현되었습니다. 향후 포맷이 변경될 경우 정규식 업데이트가 필요할 수 있습니다.
noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendServiceImpl.kt (1)
257-270: LGTM!캐시된 휴가 정보를 조회하는 로직이 명확하게 구현되었습니다.
noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendService.kt
Show resolved
Hide resolved
...re/core-api/src/main/kotlin/noweekend/core/api/controller/v1/docs/RecommendControllerDocs.kt
Show resolved
Hide resolved
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/controller/ChatbotController.kt
Outdated
Show resolved
Hide resolved
noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/RecommendController.kt
Outdated
Show resolved
Hide resolved
noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/vacation/AiVacation.kt
Show resolved
Hide resolved
| for ((idx, datesChunk) in chunkedDates.withIndex()) { | ||
| val maxAttempts = 10 | ||
| var attempt = 0 | ||
| var chunkContent: String? | ||
|
|
||
| while (true) { | ||
| try { | ||
| val detailPrompt = prompt.detailedPlanPrompt( | ||
| request, | ||
| datesChunk, | ||
| idx * chunkSize, | ||
| allDates.size, | ||
| usedItems, | ||
| ) | ||
| val userJson = objectMapper.writeValueAsString( | ||
| mapOf( | ||
| "dates" to datesChunk, | ||
| "profile" to minimalProfileMap(request), | ||
| "totalDays" to allDates.size, | ||
| ), | ||
| ) | ||
| val resp = chatClient.prompt() | ||
| .system(detailPrompt) | ||
| .user(userJson) | ||
| .call() | ||
| val content = resp.content() ?: error("Empty chunk") | ||
| usedItems += extractUsedItems(content) | ||
| chunkCache[idx] = content | ||
| return@mapIndexed content | ||
| } catch (e: NonTransientAiException) { | ||
| val cause = e.cause | ||
| val status = (cause as? RestClientResponseException)?.statusCode?.value() | ||
| val headers = (cause as? RestClientResponseException)?.responseHeaders | ||
|
|
||
| if (status == 429) { | ||
| val retryAfterSec = headers | ||
| ?.getFirst("retry-after") | ||
| ?.toLongOrNull() | ||
| ?: 180L | ||
|
|
||
| logger.warn("429 rate limit hit, waiting $retryAfterSec seconds before retry") | ||
| Thread.sleep(retryAfterSec * 1000) | ||
| continue | ||
| chunkContent = resp.content() ?: error("Empty chunk at index $idx") | ||
| usedItems += extractUsedItems(chunkContent) | ||
| break // 성공했으면 while 빠져나감 | ||
| } catch (e: Exception) { | ||
| attempt++ | ||
| if (attempt >= maxAttempts) { | ||
| throw IllegalStateException("Chunk $idx 생성 실패: ${e.message}", e) | ||
| } | ||
| throw e | ||
| // 재시도 딜레이 추가 | ||
| Thread.sleep(1000) | ||
| } | ||
| } | ||
| itineraryChunks += chunkContent ?: "" // null이 올 일은 없음(실패면 위에서 throw) | ||
| } |
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
재시도 로직 개선 필요
현재 재시도 로직에 몇 가지 개선 사항이 있습니다:
Thread.sleep은 스레드를 블로킹합니다- 고정된 1초 지연은 최적화되지 않았습니다
- 지수 백오프가 없습니다
- val maxAttempts = 10
- var attempt = 0
- var chunkContent: String?
-
- while (true) {
- try {
- val detailPrompt = prompt.detailedPlanPrompt(
- request,
- datesChunk,
- idx * chunkSize,
- allDates.size,
- usedItems,
- )
- val userJson = objectMapper.writeValueAsString(
- mapOf(
- "dates" to datesChunk,
- "profile" to minimalProfileMap(request),
- "totalDays" to allDates.size,
- ),
- )
- val resp = chatClient.prompt()
- .system(detailPrompt)
- .user(userJson)
- .call()
- chunkContent = resp.content() ?: error("Empty chunk at index $idx")
- usedItems += extractUsedItems(chunkContent)
- break // 성공했으면 while 빠져나감
- } catch (e: Exception) {
- attempt++
- if (attempt >= maxAttempts) {
- throw IllegalStateException("Chunk $idx 생성 실패: ${e.message}", e)
- }
- // 재시도 딜레이 추가
- Thread.sleep(1000)
- }
- }
+ val maxAttempts = 10
+ val backoffDelays = listOf(1000L, 2000L, 4000L, 8000L, 16000L)
+ var lastException: Exception? = null
+
+ for (attempt in 0 until maxAttempts) {
+ try {
+ val detailPrompt = prompt.detailedPlanPrompt(
+ request,
+ datesChunk,
+ idx * chunkSize,
+ allDates.size,
+ usedItems,
+ )
+ val userJson = objectMapper.writeValueAsString(
+ mapOf(
+ "dates" to datesChunk,
+ "profile" to minimalProfileMap(request),
+ "totalDays" to allDates.size,
+ ),
+ )
+ val resp = chatClient.prompt()
+ .system(detailPrompt)
+ .user(userJson)
+ .call()
+ val chunkContent = resp.content() ?: error("Empty chunk at index $idx")
+ usedItems += extractUsedItems(chunkContent)
+ itineraryChunks += chunkContent
+ break
+ } catch (e: Exception) {
+ lastException = e
+ if (attempt < maxAttempts - 1) {
+ val delay = backoffDelays.getOrElse(attempt) { backoffDelays.last() }
+ Thread.sleep(delay)
+ }
+ }
+ }
+
+ if (lastException != null) {
+ throw IllegalStateException("Chunk $idx 생성 실패: ${lastException.message}", lastException)
+ }📝 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.
| for ((idx, datesChunk) in chunkedDates.withIndex()) { | |
| val maxAttempts = 10 | |
| var attempt = 0 | |
| var chunkContent: String? | |
| while (true) { | |
| try { | |
| val detailPrompt = prompt.detailedPlanPrompt( | |
| request, | |
| datesChunk, | |
| idx * chunkSize, | |
| allDates.size, | |
| usedItems, | |
| ) | |
| val userJson = objectMapper.writeValueAsString( | |
| mapOf( | |
| "dates" to datesChunk, | |
| "profile" to minimalProfileMap(request), | |
| "totalDays" to allDates.size, | |
| ), | |
| ) | |
| val resp = chatClient.prompt() | |
| .system(detailPrompt) | |
| .user(userJson) | |
| .call() | |
| val content = resp.content() ?: error("Empty chunk") | |
| usedItems += extractUsedItems(content) | |
| chunkCache[idx] = content | |
| return@mapIndexed content | |
| } catch (e: NonTransientAiException) { | |
| val cause = e.cause | |
| val status = (cause as? RestClientResponseException)?.statusCode?.value() | |
| val headers = (cause as? RestClientResponseException)?.responseHeaders | |
| if (status == 429) { | |
| val retryAfterSec = headers | |
| ?.getFirst("retry-after") | |
| ?.toLongOrNull() | |
| ?: 180L | |
| logger.warn("429 rate limit hit, waiting $retryAfterSec seconds before retry") | |
| Thread.sleep(retryAfterSec * 1000) | |
| continue | |
| chunkContent = resp.content() ?: error("Empty chunk at index $idx") | |
| usedItems += extractUsedItems(chunkContent) | |
| break // 성공했으면 while 빠져나감 | |
| } catch (e: Exception) { | |
| attempt++ | |
| if (attempt >= maxAttempts) { | |
| throw IllegalStateException("Chunk $idx 생성 실패: ${e.message}", e) | |
| } | |
| throw e | |
| // 재시도 딜레이 추가 | |
| Thread.sleep(1000) | |
| } | |
| } | |
| itineraryChunks += chunkContent ?: "" // null이 올 일은 없음(실패면 위에서 throw) | |
| } | |
| for ((idx, datesChunk) in chunkedDates.withIndex()) { | |
| val maxAttempts = 10 | |
| val backoffDelays = listOf(1000L, 2000L, 4000L, 8000L, 16000L) | |
| var lastException: Exception? = null | |
| for (attempt in 0 until maxAttempts) { | |
| try { | |
| val detailPrompt = prompt.detailedPlanPrompt( | |
| request, | |
| datesChunk, | |
| idx * chunkSize, | |
| allDates.size, | |
| usedItems, | |
| ) | |
| val userJson = objectMapper.writeValueAsString( | |
| mapOf( | |
| "dates" to datesChunk, | |
| "profile" to minimalProfileMap(request), | |
| "totalDays" to allDates.size, | |
| ), | |
| ) | |
| val resp = chatClient.prompt() | |
| .system(detailPrompt) | |
| .user(userJson) | |
| .call() | |
| val chunkContent = resp.content() ?: error("Empty chunk at index $idx") | |
| usedItems += extractUsedItems(chunkContent) | |
| itineraryChunks += chunkContent | |
| break | |
| } catch (e: Exception) { | |
| lastException = e | |
| if (attempt < maxAttempts - 1) { | |
| val delay = backoffDelays.getOrElse(attempt) { backoffDelays.last() } | |
| Thread.sleep(delay) | |
| } | |
| } | |
| } | |
| if (lastException != null) { | |
| throw IllegalStateException("Chunk $idx 생성 실패: ${lastException.message}", lastException) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
noweekend-mcp/mcp-host/src/main/kotlin/noweekend/mcphost/service/ChatbotService.kt
between lines 155 and 193, the retry logic uses Thread.sleep with a fixed
1-second delay, which blocks the thread and lacks exponential backoff. Replace
Thread.sleep with a non-blocking delay mechanism if possible, and implement
exponential backoff by increasing the delay time after each failed attempt to
optimize retry intervals and reduce resource blocking.
요약
작업 내용
Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
기타