-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/414 : Ai피드백 Resilence4j를 활용한 CB,Retry 적용 #415
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
Changes from 3 commits
f9e1197
5e385ae
000b232
ca531ba
0e236b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.example.cs25service.domain.ai.resilience; | ||
|
|
||
| import io.github.resilience4j.circuitbreaker.CircuitBreaker; | ||
| import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; | ||
| import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator; | ||
| import io.github.resilience4j.reactor.retry.RetryOperator; | ||
| import io.github.resilience4j.retry.Retry; | ||
| import io.github.resilience4j.retry.RetryRegistry; | ||
| import java.util.function.Supplier; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
| import reactor.core.publisher.Flux; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class AiResilience { | ||
|
|
||
| private final CircuitBreakerRegistry cbRegistry; | ||
| private final RetryRegistry retryRegistry; | ||
|
|
||
| /** | ||
| * 동기 호출: Retry → CircuitBreaker 순서 | ||
| */ | ||
| public <T> T executeSync(String name, Supplier<T> supplier) { | ||
| CircuitBreaker cb = cbRegistry.circuitBreaker(name); | ||
| Retry retry = retryRegistry.retry(name); | ||
|
|
||
| Supplier<T> withRetry = Retry.decorateSupplier(retry, supplier); | ||
| Supplier<T> withCb = CircuitBreaker.decorateSupplier(cb, withRetry); | ||
|
|
||
| return withCb.get(); | ||
| } | ||
|
|
||
| /** | ||
| * Flux 스트리밍: RetryOperator → CircuitBreakerOperator 순서 | ||
| */ | ||
| public <T> Flux<T> executeStream(String name, Supplier<Flux<T>> supplier) { | ||
| CircuitBreaker cb = cbRegistry.circuitBreaker(name); | ||
| Retry retry = retryRegistry.retry(name); | ||
|
|
||
| return supplier.get() | ||
| .transformDeferred(RetryOperator.of(retry)) | ||
| .transformDeferred(CircuitBreakerOperator.of(cb)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,12 +67,31 @@ spring.ai.mcp.client.enabled=true | |
| spring.ai.mcp.client.type=SYNC | ||
| spring.ai.mcp.client.request-timeout=60s | ||
| spring.ai.mcp.client.root-change-notification=false | ||
| # STDIO Connect: Brave Search | ||
| # Brave Search | ||
| spring.ai.mcp.client.stdio.connections.brave.command=server-brave-search | ||
| spring.ai.mcp.client.stdio.connections.brave.args[0]=--transport | ||
| spring.ai.mcp.client.stdio.connections.brave.args[1]=stdio | ||
| spring.ai.mcp.client.stdio.connections.brave.env.BRAVE_API_KEY=${BRAVE_API_KEY} | ||
| spring.autoconfigure.exclude=org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration | ||
| # CircuitBreaker | ||
| resilience4j.circuitbreaker.configs.default.slidingWindowType=COUNT_BASED | ||
| resilience4j.circuitbreaker.configs.default.slidingWindowSize=10 | ||
| resilience4j.circuitbreaker.configs.default.failureRateThreshold=50 | ||
| resilience4j.circuitbreaker.configs.default.slowCallDurationThreshold=5s | ||
| resilience4j.circuitbreaker.configs.default.slowCallRateThreshold=70 | ||
| resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=6 | ||
| resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=30m | ||
| resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=1 | ||
| resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true | ||
| resilience4j.circuitbreaker.instances.openai.baseConfig=default | ||
| resilience4j.circuitbreaker.instances.claude.baseConfig=default | ||
| # Retry | ||
| resilience4j.retry.configs.default.maxAttempts=2 | ||
| resilience4j.retry.configs.default.waitDuration=200ms | ||
| resilience4j.retry.configs.default.enableExponentialBackoff=true | ||
| resilience4j.retry.configs.default.exponentialBackoffMultiplier=2 | ||
| resilience4j.retry.instances.openai.baseConfig=default | ||
| resilience4j.retry.instances.claude.baseConfig=default | ||
|
Comment on lines
+93
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 4xx 클라이언트 오류는 Retry/CB 집계에서 제외하는 것을 강력 권장합니다. 현재 스트림 경로에서 오류가 아래 설정 추가를 제안드립니다(예: default config에 4xx 무시): resilience4j.retry.configs.default.maxAttempts=2
resilience4j.retry.configs.default.waitDuration=200ms
resilience4j.retry.configs.default.enableExponentialBackoff=true
resilience4j.retry.configs.default.exponentialBackoffMultiplier=2
+resilience4j.retry.configs.default.ignoreExceptions[0]=org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest
+resilience4j.retry.configs.default.ignoreExceptions[1]=org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized
+resilience4j.retry.configs.default.ignoreExceptions[2]=org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden
+resilience4j.retry.configs.default.ignoreExceptions[3]=org.springframework.web.reactive.function.client.WebClientResponseException$NotFound
resilience4j.retry.instances.openai.baseConfig=default
resilience4j.retry.instances.claude.baseConfig=defaultCircuitBreaker 쪽도 동일하게 4xx를 무시하도록 설정하면 더 안정적입니다. resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=6
resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=30m
resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=1
resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[0]=org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[1]=org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[2]=org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[3]=org.springframework.web.reactive.function.client.WebClientResponseException$NotFound참고: 이 설정이 효과를 내기 위해서는 아래 클라이언트 코드에서 🤖 Prompt for AI Agents |
||
| spring.mail.host=smtp.gmail.com | ||
| spring.mail.port=587 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.