# AI Service 전체 메서드 워크플로우 ## 1. AI 추천 생성 워크플로우 (Kafka 기반 비동기) ```mermaid sequenceDiagram participant ES as Event Service participant Kafka as Kafka Topic participant Consumer as AIJobConsumer participant ARS as AIRecommendationService participant JSS as JobStatusService participant TAS as TrendAnalysisService participant CS as CacheService participant CAC as ClaudeApiClient participant Claude as Claude API participant Redis as Redis %% 1. Event Service가 Kafka 메시지 발행 ES->>Kafka: Publish AIJobMessage
(ai-event-generation-job topic) %% 2. Kafka Consumer가 메시지 수신 Kafka->>Consumer: consume(AIJobMessage) Note over Consumer: @KafkaListener
groupId: ai-service-consumers %% 3. AI 추천 생성 시작 Consumer->>ARS: generateRecommendations(message) activate ARS %% 4. Job 상태: PROCESSING (10%) ARS->>JSS: updateJobStatus(jobId, PROCESSING, "트렌드 분석 중") JSS->>CS: saveJobStatus(jobId, status) CS->>Redis: SET ai:job:status:{jobId} %% 5. 트렌드 분석 ARS->>ARS: analyzeTrend(message) ARS->>CS: getTrend(industry, region) CS->>Redis: GET ai:trend:{industry}:{region} alt 캐시 HIT Redis-->>CS: TrendAnalysis (cached) CS-->>ARS: TrendAnalysis else 캐시 MISS ARS->>TAS: analyzeTrend(industry, region) activate TAS %% Circuit Breaker 적용 TAS->>TAS: circuitBreakerManager.executeWithCircuitBreaker() TAS->>CAC: sendMessage(apiKey, version, request) CAC->>Claude: POST /v1/messages Note over Claude: Model: claude-sonnet-4-5
System: "트렌드 분석 전문가"
Prompt: 업종/지역/계절 트렌드 Claude-->>CAC: ClaudeResponse CAC-->>TAS: ClaudeResponse TAS->>TAS: parseResponse(responseText) TAS-->>ARS: TrendAnalysis deactivate TAS %% 트렌드 캐싱 ARS->>CS: saveTrend(industry, region, analysis) CS->>Redis: SET ai:trend:{industry}:{region} (TTL: 1시간) end %% 6. Job 상태: PROCESSING (50%) ARS->>JSS: updateJobStatus(jobId, PROCESSING, "이벤트 추천안 생성 중") JSS->>CS: saveJobStatus(jobId, status) CS->>Redis: SET ai:job:status:{jobId} %% 7. 이벤트 추천안 생성 ARS->>ARS: createRecommendations(message, trendAnalysis) ARS->>ARS: circuitBreakerManager.executeWithCircuitBreaker() ARS->>CAC: sendMessage(apiKey, version, request) CAC->>Claude: POST /v1/messages Note over Claude: Model: claude-sonnet-4-5
System: "이벤트 기획 전문가"
Prompt: 3가지 추천안 생성 Claude-->>CAC: ClaudeResponse CAC-->>ARS: ClaudeResponse ARS->>ARS: parseRecommendationResponse(responseText) %% 8. Job 상태: PROCESSING (90%) ARS->>JSS: updateJobStatus(jobId, PROCESSING, "결과 저장 중") JSS->>CS: saveJobStatus(jobId, status) CS->>Redis: SET ai:job:status:{jobId} %% 9. 결과 저장 ARS->>CS: saveRecommendation(eventId, result) CS->>Redis: SET ai:recommendation:{eventId} (TTL: 24시간) %% 10. Job 상태: COMPLETED (100%) ARS->>JSS: updateJobStatus(jobId, COMPLETED, "AI 추천 완료") JSS->>CS: saveJobStatus(jobId, status) CS->>Redis: SET ai:job:status:{jobId} deactivate ARS %% 11. Kafka ACK Consumer->>Kafka: acknowledgment.acknowledge() ``` --- ## 2. Job 상태 조회 워크플로우 (동기) ```mermaid sequenceDiagram participant ES as Event Service participant Controller as InternalJobController participant JSS as JobStatusService participant CS as CacheService participant Redis as Redis %% 1. Event Service가 Job 상태 조회 ES->>Controller: GET /api/v1/ai-service/internal/jobs/{jobId}/status %% 2. Job 상태 조회 Controller->>JSS: getJobStatus(jobId) activate JSS JSS->>CS: getJobStatus(jobId) CS->>Redis: GET ai:job:status:{jobId} alt 상태 존재 Redis-->>CS: JobStatusResponse CS-->>JSS: Object (JobStatusResponse) JSS->>JSS: objectMapper.convertValue() JSS-->>Controller: JobStatusResponse Controller-->>ES: 200 OK + JobStatusResponse else 상태 없음 Redis-->>CS: null CS-->>JSS: null JSS-->>Controller: JobNotFoundException Controller-->>ES: 404 Not Found end deactivate JSS ``` --- ## 3. AI 추천 결과 조회 워크플로우 (동기) ```mermaid sequenceDiagram participant ES as Event Service participant Controller as InternalRecommendationController participant ARS as AIRecommendationService participant CS as CacheService participant Redis as Redis %% 1. Event Service가 AI 추천 결과 조회 ES->>Controller: GET /api/v1/ai-service/internal/recommendations/{eventId} %% 2. 추천 결과 조회 Controller->>ARS: getRecommendation(eventId) activate ARS ARS->>CS: getRecommendation(eventId) CS->>Redis: GET ai:recommendation:{eventId} alt 결과 존재 Redis-->>CS: AIRecommendationResult CS-->>ARS: Object (AIRecommendationResult) ARS->>ARS: objectMapper.convertValue() ARS-->>Controller: AIRecommendationResult Controller-->>ES: 200 OK + AIRecommendationResult else 결과 없음 Redis-->>CS: null CS-->>ARS: null ARS-->>Controller: RecommendationNotFoundException Controller-->>ES: 404 Not Found end deactivate ARS ``` --- ## 4. 헬스체크 워크플로우 (동기) ```mermaid sequenceDiagram participant Client as Client/Actuator participant Controller as HealthController participant Redis as Redis %% 1. 헬스체크 요청 Client->>Controller: GET /api/v1/ai-service/health %% 2. Redis 상태 확인 Controller->>Controller: checkRedis() alt RedisTemplate 존재 Controller->>Redis: PING alt Redis 정상 Redis-->>Controller: PONG Controller->>Controller: redisStatus = UP else Redis 오류 Redis-->>Controller: Exception Controller->>Controller: redisStatus = DOWN end else RedisTemplate 없음 Controller->>Controller: redisStatus = UNKNOWN end %% 3. 전체 상태 판단 alt Redis DOWN Controller->>Controller: overallStatus = DEGRADED else Redis UP/UNKNOWN Controller->>Controller: overallStatus = UP end %% 4. 응답 Controller-->>Client: 200 OK + HealthCheckResponse ``` --- ## 5. 주요 컴포넌트 메서드 목록 ### 5.1 Controller Layer #### InternalJobController | 메서드 | HTTP | 엔드포인트 | 설명 | |--------|------|-----------|------| | `getJobStatus(jobId)` | GET | `/api/v1/ai-service/internal/jobs/{jobId}/status` | Job 상태 조회 | | `createTestJob(jobId)` | GET | `/api/v1/ai-service/internal/jobs/debug/create-test-job/{jobId}` | 테스트 Job 생성 (디버그) | #### InternalRecommendationController | 메서드 | HTTP | 엔드포인트 | 설명 | |--------|------|-----------|------| | `getRecommendation(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/{eventId}` | AI 추천 결과 조회 | | `debugRedisKeys()` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-keys` | Redis 모든 키 조회 | | `debugRedisKey(key)` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-key/{key}` | Redis 특정 키 조회 | | `searchAllDatabases()` | GET | `/api/v1/ai-service/internal/recommendations/debug/search-all-databases` | 전체 DB 검색 | | `createTestData(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/debug/create-test-data/{eventId}` | 테스트 데이터 생성 | #### HealthController | 메서드 | HTTP | 엔드포인트 | 설명 | |--------|------|-----------|------| | `healthCheck()` | GET | `/api/v1/ai-service/health` | 서비스 헬스체크 | | `checkRedis()` | - | (내부) | Redis 연결 확인 | --- ### 5.2 Service Layer #### AIRecommendationService | 메서드 | 호출자 | 설명 | |--------|-------|------| | `getRecommendation(eventId)` | Controller | Redis에서 추천 결과 조회 | | `generateRecommendations(message)` | AIJobConsumer | AI 추천 생성 (전체 프로세스) | | `analyzeTrend(message)` | 내부 | 트렌드 분석 (캐시 확인 포함) | | `createRecommendations(message, trendAnalysis)` | 내부 | 이벤트 추천안 생성 | | `callClaudeApiForRecommendations(message, trendAnalysis)` | 내부 | Claude API 호출 (추천안) | | `buildRecommendationPrompt(message, trendAnalysis)` | 내부 | 추천안 프롬프트 생성 | | `parseRecommendationResponse(responseText)` | 내부 | 추천안 응답 파싱 | | `parseEventRecommendation(node)` | 내부 | EventRecommendation 파싱 | | `parseRange(node)` | 내부 | Range 객체 파싱 | | `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 | #### TrendAnalysisService | 메서드 | 호출자 | 설명 | |--------|-------|------| | `analyzeTrend(industry, region)` | AIRecommendationService | 트렌드 분석 수행 | | `callClaudeApi(industry, region)` | 내부 | Claude API 호출 (트렌드) | | `buildPrompt(industry, region)` | 내부 | 트렌드 분석 프롬프트 생성 | | `parseResponse(responseText)` | 내부 | 트렌드 응답 파싱 | | `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 | | `parseTrendKeywords(arrayNode)` | 내부 | TrendKeyword 리스트 파싱 | #### JobStatusService | 메서드 | 호출자 | 설명 | |--------|-------|------| | `getJobStatus(jobId)` | Controller | Job 상태 조회 | | `updateJobStatus(jobId, status, message)` | AIRecommendationService | Job 상태 업데이트 | | `calculateProgress(status)` | 내부 | 상태별 진행률 계산 | #### CacheService | 메서드 | 호출자 | 설명 | |--------|-------|------| | `set(key, value, ttlSeconds)` | 내부 | 범용 캐시 저장 | | `get(key)` | 내부 | 범용 캐시 조회 | | `delete(key)` | 외부 | 캐시 삭제 | | `saveJobStatus(jobId, status)` | JobStatusService | Job 상태 저장 | | `getJobStatus(jobId)` | JobStatusService | Job 상태 조회 | | `saveRecommendation(eventId, recommendation)` | AIRecommendationService | AI 추천 결과 저장 | | `getRecommendation(eventId)` | AIRecommendationService | AI 추천 결과 조회 | | `saveTrend(industry, region, trend)` | AIRecommendationService | 트렌드 분석 결과 저장 | | `getTrend(industry, region)` | AIRecommendationService | 트렌드 분석 결과 조회 | --- ### 5.3 Consumer Layer #### AIJobConsumer | 메서드 | 트리거 | 설명 | |--------|-------|------| | `consume(message, topic, offset, ack)` | Kafka Message | Kafka 메시지 수신 및 처리 | --- ### 5.4 Client Layer #### ClaudeApiClient (Feign) | 메서드 | 호출자 | 설명 | |--------|-------|------| | `sendMessage(apiKey, anthropicVersion, request)` | TrendAnalysisService, AIRecommendationService | Claude API 호출 | --- ## 6. Redis 캐시 키 구조 | 키 패턴 | 설명 | TTL | |--------|------|-----| | `ai:job:status:{jobId}` | Job 상태 정보 | 24시간 (86400초) | | `ai:recommendation:{eventId}` | AI 추천 결과 | 24시간 (86400초) | | `ai:trend:{industry}:{region}` | 트렌드 분석 결과 | 1시간 (3600초) | --- ## 7. Claude API 호출 정보 ### 7.1 트렌드 분석 - **URL**: `https://api.anthropic.com/v1/messages` - **Model**: `claude-sonnet-4-5-20250929` - **Max Tokens**: 4096 - **Temperature**: 0.7 - **System Prompt**: "당신은 마케팅 트렌드 분석 전문가입니다. 업종별, 지역별 트렌드를 분석하고 인사이트를 제공합니다." - **응답 형식**: JSON (industryTrends, regionalTrends, seasonalTrends) ### 7.2 이벤트 추천안 생성 - **URL**: `https://api.anthropic.com/v1/messages` - **Model**: `claude-sonnet-4-5-20250929` - **Max Tokens**: 4096 - **Temperature**: 0.7 - **System Prompt**: "당신은 소상공인을 위한 마케팅 이벤트 기획 전문가입니다. 트렌드 분석을 바탕으로 실행 가능한 이벤트 추천안을 제공합니다." - **응답 형식**: JSON (recommendations: 3가지 옵션) --- ## 8. Circuit Breaker 설정 ### 적용 대상 - `claudeApi`: 모든 Claude API 호출 ### 설정값 ```yaml failure-rate-threshold: 50% slow-call-duration-threshold: 60초 sliding-window-size: 10 minimum-number-of-calls: 5 wait-duration-in-open-state: 60초 timeout-duration: 300초 (5분) ``` ### Fallback 메서드 - `AIServiceFallback.getDefaultTrendAnalysis()`: 기본 트렌드 분석 - `AIServiceFallback.getDefaultRecommendations()`: 기본 추천안 --- ## 9. 에러 처리 ### Exception 종류 | Exception | HTTP Code | 발생 조건 | |-----------|-----------|---------| | `RecommendationNotFoundException` | 404 | Redis에 추천 결과 없음 | | `JobNotFoundException` | 404 | Redis에 Job 상태 없음 | | `AIServiceException` | 500 | AI 서비스 내부 오류 | ### 에러 응답 예시 ```json { "timestamp": "2025-10-30T15:30:00", "status": 404, "error": "Not Found", "message": "추천 결과를 찾을 수 없습니다: eventId=evt-123", "path": "/api/v1/ai-service/internal/recommendations/evt-123" } ``` --- ## 10. 로깅 레벨 ```yaml com.kt.ai: DEBUG org.springframework.kafka: INFO org.springframework.data.redis: INFO io.github.resilience4j: DEBUG ```