diff --git a/app/services/ai_analyzer.py b/app/services/ai_analyzer.py index ff385fe..b43aaab 100644 --- a/app/services/ai_analyzer.py +++ b/app/services/ai_analyzer.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod import anthropic import re +import time from app.models.feedback import AnalysisResult @@ -66,42 +67,60 @@ class ClaudeAnalyzer(AIAnalyzer): # Design prompt for Claude API (T075) prompt = self._build_analysis_prompt(feedback_text, target_language) - try: - # Call Claude API with timeout (T074, T080) - response = self._call_claude_api(prompt, timeout=45) + # Retry logic for transient failures (T081) + max_retries = 3 + retry_delay = 1 # seconds - # Extract analysis components from response - raw_analysis = response.content[0].text + for attempt in range(max_retries): + try: + # Call Claude API with timeout (T074, T080) + response = self._call_claude_api(prompt, timeout=45) - # Extract category (T077) - category = self._extract_category(raw_analysis) + # Extract analysis components from response + raw_analysis = response.content[0].text - # Detect original language (T076) - original_language = self._extract_language(raw_analysis) + # Extract category (T077) + category = self._extract_category(raw_analysis) - # Extract summary (T078) - summary = self._extract_summary(raw_analysis) + # Detect original language (T076) + original_language = self._extract_language(raw_analysis) - # Extract translation (T079) - translation = self._extract_translation(raw_analysis) + # Extract summary (T078) + summary = self._extract_summary(raw_analysis) - return AnalysisResult( - category=category, - original_language=original_language, - summary=summary, - translation=translation, - raw_analysis=raw_analysis - ) + # Extract translation (T079) + translation = self._extract_translation(raw_analysis) - except anthropic.APITimeoutError as e: - # Handle API timeouts (T080) - raise Exception(f"Claude API timeout after 45s: {str(e)}") - except anthropic.APIError as e: - # Handle API errors with retry logic (T081) - raise Exception(f"Claude API error: {str(e)}") - except Exception as e: - # General error handling - raise Exception(f"Analysis failed: {str(e)}") + return AnalysisResult( + category=category, + original_language=original_language, + summary=summary, + translation=translation, + raw_analysis=raw_analysis + ) + + except anthropic.APITimeoutError as e: + # Handle API timeouts (T080) - don't retry timeouts + raise Exception(f"Claude API timeout after 45s: {str(e)}") + + except anthropic.APIError as e: + # Handle API errors with retry logic (T081) + # Check if this is a transient error (rate limit, server error) + error_type = type(e).__name__ + is_retryable = any(x in error_type.lower() for x in ['ratelimit', 'server', 'unavailable', 'overloaded']) + + if is_retryable and attempt < max_retries - 1: + # Wait before retrying with exponential backoff + wait_time = retry_delay * (2 ** attempt) + time.sleep(wait_time) + continue + else: + # Not retryable or max retries exceeded + raise Exception(f"Claude API error: {str(e)}") + + except Exception as e: + # General error handling - don't retry + raise Exception(f"Analysis failed: {str(e)}") def _build_analysis_prompt(self, feedback_text, target_language): """Build the analysis prompt for Claude diff --git a/specs/001-build-an-application/tasks.md b/specs/001-build-an-application/tasks.md index 6ed51ed..7ca7234 100644 --- a/specs/001-build-an-application/tasks.md +++ b/specs/001-build-an-application/tasks.md @@ -129,37 +129,37 @@ ### Tests for User Story 2 (MUST WRITE FIRST) ⚠️ -- [ ] T065 [P] [US2] Unit test for AIAnalyzer interface in tests/unit/test_ai_analyzer.py -- [ ] T066 [P] [US2] Unit test for ClaudeAnalyzer categorization in tests/unit/test_ai_analyzer.py -- [ ] T067 [P] [US2] Unit test for ClaudeAnalyzer translation in tests/unit/test_ai_analyzer.py -- [ ] T068 [P] [US2] Unit test for ClaudeAnalyzer summary generation in tests/unit/test_ai_analyzer.py -- [ ] T069 [P] [US2] Unit test for analysis error handling in tests/unit/test_ai_analyzer.py -- [ ] T070 [P] [US2] Integration test for full AI analysis flow in tests/integration/test_ai_analysis_flow.py +- [X] T065 [P] [US2] Unit test for AIAnalyzer interface in tests/unit/test_ai_analyzer.py +- [X] T066 [P] [US2] Unit test for ClaudeAnalyzer categorization in tests/unit/test_ai_analyzer.py +- [X] T067 [P] [US2] Unit test for ClaudeAnalyzer translation in tests/unit/test_ai_analyzer.py +- [X] T068 [P] [US2] Unit test for ClaudeAnalyzer summary generation in tests/unit/test_ai_analyzer.py +- [X] T069 [P] [US2] Unit test for analysis error handling in tests/unit/test_ai_analyzer.py +- [X] T070 [P] [US2] Integration test for full AI analysis flow in tests/integration/test_ai_analysis_flow.py ### Implementation for User Story 2 -- [ ] T071 [P] [US2] Create AIAnalyzer abstract base class in app/services/ai_analyzer.py -- [ ] T072 [P] [US2] Create AnalysisResult dataclass in app/models/feedback.py -- [ ] T073 [US2] Implement ClaudeAnalyzer class in app/services/ai_analyzer.py (depends on T071) -- [ ] T074 [US2] Implement analyze_feedback method in ClaudeAnalyzer using Anthropic SDK -- [ ] T075 [US2] Design prompt for Claude API (categorize + summarize + translate in single call) -- [ ] T076 [US2] Implement language detection in ClaudeAnalyzer -- [ ] T077 [US2] Implement category extraction from Claude response -- [ ] T078 [US2] Implement summary extraction from Claude response -- [ ] T079 [US2] Implement translation extraction from Claude response -- [ ] T080 [US2] Add error handling for API timeouts (45s timeout) -- [ ] T081 [US2] Add retry logic for transient API failures -- [ ] T082 [US2] Implement save_analysis method in FeedbackStorageService (writes analysis.md) -- [ ] T083 [US2] Create analysis markdown template format in FeedbackStorageService -- [ ] T084 [US2] Implement background analysis task using Python threading module -- [ ] T085 [US2] Integrate background analysis trigger in submission POST route after successful save -- [ ] T086 [US2] Update feedback status to "analyzing" when background task starts -- [ ] T087 [US2] Update feedback status to "analyzed" when analysis succeeds -- [ ] T088 [US2] Update feedback status to "analysis_failed" on error -- [ ] T089 [US2] Store detected language in metadata.yaml original_language field -- [ ] T090 [US2] Add ANTHROPIC_API_KEY to .env.example file -- [ ] T091 [US2] Verify analysis preserves original content.txt file (FR-016) -- [ ] T092 [US2] Verify images are stored but not analyzed via OCR (FR-021) +- [X] T071 [P] [US2] Create AIAnalyzer abstract base class in app/services/ai_analyzer.py +- [X] T072 [P] [US2] Create AnalysisResult dataclass in app/models/feedback.py +- [X] T073 [US2] Implement ClaudeAnalyzer class in app/services/ai_analyzer.py (depends on T071) +- [X] T074 [US2] Implement analyze_feedback method in ClaudeAnalyzer using Anthropic SDK +- [X] T075 [US2] Design prompt for Claude API (categorize + summarize + translate in single call) +- [X] T076 [US2] Implement language detection in ClaudeAnalyzer +- [X] T077 [US2] Implement category extraction from Claude response +- [X] T078 [US2] Implement summary extraction from Claude response +- [X] T079 [US2] Implement translation extraction from Claude response +- [X] T080 [US2] Add error handling for API timeouts (45s timeout) +- [X] T081 [US2] Add retry logic for transient API failures +- [X] T082 [US2] Implement save_analysis method in FeedbackStorageService (writes analysis.md) +- [X] T083 [US2] Create analysis markdown template format in FeedbackStorageService +- [X] T084 [US2] Implement background analysis task using Python threading module +- [X] T085 [US2] Integrate background analysis trigger in submission POST route after successful save +- [X] T086 [US2] Update feedback status to "analyzing" when background task starts +- [X] T087 [US2] Update feedback status to "analyzed" when analysis succeeds +- [X] T088 [US2] Update feedback status to "analysis_failed" on error +- [X] T089 [US2] Store detected language in metadata.yaml original_language field +- [X] T090 [US2] Add ANTHROPIC_API_KEY to .env.example file +- [X] T091 [US2] Verify analysis preserves original content.txt file (FR-016) +- [X] T092 [US2] Verify images are stored but not analyzed via OCR (FR-021) **Checkpoint**: At this point, User Stories 1 AND 2 work together - feedback is submitted AND automatically analyzed