feat: DeBERTa emotion detection API deployment - #165
Conversation
- Enhanced BERT-based emotion classifier for journal entries - Multi-label emotion classification (28 emotions from GoEmotions) - Temperature scaling for calibrated predictions - Comprehensive emotion labels and descriptions - Standalone test script for validation - Configuration file with SAMO-specific optimizations - Error handling and logging improvements Files: - src/models/emotion_detection/samo_bert_emotion_classifier.py - src/models/emotion_detection/emotion_labels.py - configs/samo_emotion_detection_config.yaml - test_samo_emotion_detection_standalone.py This completes PR-3 of the surgical breakdown plan.
- Fixed NameError for classifier_dropout_prob and freeze_bert_layers - Updated constructor to use self.classifier_dropout_prob and self.freeze_bert_layers - Model now initializes correctly and passes standalone tests - Maintains 110M parameters with 66M frozen BERT layers Part of PR-3: Emotion Detection model completion
- Created FastAPI-based unified server integrating T5, Whisper, and BERT models - Individual endpoints: /summarize, /transcribe, /detect-emotions - Combined pipeline endpoint: /process-audio (transcription -> summary -> emotions) - Comprehensive error handling and validation - Health monitoring endpoint - CORS support for web applications - Request/response models with Pydantic validation - Background task cleanup for uploaded files Part of PR-4: Unified API Server implementation
- Added API configuration file (samo_api_config.yaml) with model settings - Created comprehensive test suite (test_unified_api_server.py) with mocked tests - Added startup script (start_api_server.py) with CLI arguments - Updated API requirements file with all necessary dependencies - Includes health checks, validation, error handling, and combined pipeline tests Part of PR-4: Unified API Server implementation
- Fixed emotion detection model loading (tuple unpacking issue) - Successfully tested all endpoints: /summarize, /transcribe, /detect-emotions, /process-audio - Combined pipeline working: transcription -> summarization -> emotion detection - All models loading correctly with proper error handling - API server running on http://localhost:8000 with full documentation Part of PR-4: Unified API Server - all endpoints functional and tested
- Add DeBERTa-optimized Dockerfile with AMD64 platform support - Fix API route registration for Flask-RESTX endpoints - Add comprehensive DeBERTa deployment script - Deploy 28-class emotion detection model to Cloud Run - Add deployment documentation and testing scripts API now live at: https://samo-emotion-deberta-71517823771.us-central1.run.app - Single prediction: POST /api/predict - Health check: GET /api/health - Emotions list: GET /api/emotions - 28 emotion classes with high confidence scores
Reviewer's GuideThis PR integrates a production-ready DeBERTa-based emotion detection pipeline by adding a new multi-label BERT classifier, registering its endpoints in the Cloud Run API, unifying model serving under FastAPI, and updating deployment scripts, configuration, and tests. Sequence diagram for unified audio-to-emotion pipeline endpointsequenceDiagram
participant User as actor User
participant API as SAMOUnifiedAPIServer
participant Transcriber as WhisperTranscriber
participant Summarizer as T5SummarizationModel
participant EmotionDetector as SAMOBERTEmotionClassifier
User->>API: POST /process-audio (audio file)
API->>Transcriber: transcribe(audio)
Transcriber-->>API: transcription result
API->>Summarizer: generate_summary(transcription)
Summarizer-->>API: summary result
API->>EmotionDetector: predict_emotions(transcription)
EmotionDetector-->>API: emotion result
API-->>User: CombinedProcessingResponse (transcription, summary, emotions)
Class diagram for the new SAMOBERTEmotionClassifier and related typesclassDiagram
class SAMOBERTEmotionClassifier {
+model_name: str
+num_emotions: int
+hidden_dropout_prob: float
+classifier_dropout_prob: float
+freeze_bert_layers: int
+temperature: nn.Parameter
+class_weights: Optional
+prediction_threshold: float
+bert: AutoModel
+tokenizer: AutoTokenizer
+classifier: nn.Sequential
+device: torch.device
+forward(input_ids, attention_mask, token_type_ids)
+predict_emotions(texts, threshold, top_k, batch_size)
+set_temperature(temperature)
+count_parameters()
+count_frozen_parameters()
+_init_classification_layers()
+_freeze_bert_layers(num_layers)
+unfreeze_bert_layers(num_layers)
}
class WeightedBCELoss {
+class_weights: Optional[torch.Tensor]
+reduction: str
+forward(logits, targets)
}
class EmotionDataset {
+texts: List[str]
+labels: List[List[int]]
+tokenizer: AutoTokenizer
+max_length: int
+__getitem__(idx)
+__len__()
}
SAMOBERTEmotionClassifier --> WeightedBCELoss : uses
EmotionDataset --> SAMOBERTEmotionClassifier : used for training
WeightedBCELoss --> SAMOBERTEmotionClassifier : used for training
Class diagram for the new SAMOUnifiedAPIServer and API modelsclassDiagram
class SAMOUnifiedAPIServer {
+app: FastAPI
+models: Dict[str, Any]
+_load_models()
+_setup_routes()
+_get_health_status()
+run(host, port)
}
class SummarizationRequest {
+text: str
+max_length: Optional[int]
+min_length: Optional[int]
+num_beams: Optional[int]
}
class SummarizationResponse {
+summary: str
+original_length: int
+summary_length: int
+processing_time: float
+model_info: Dict[str, Any]
}
class TranscriptionRequest {
+language: Optional[str]
+initial_prompt: Optional[str]
}
class TranscriptionResponse {
+text: str
+language: str
+confidence: float
+duration: float
+processing_time: float
+audio_quality: str
+word_count: int
+speaking_rate: float
+no_speech_probability: float
}
class EmotionDetectionRequest {
+text: str
+threshold: Optional[float]
+top_k: Optional[int]
}
class EmotionDetectionResponse {
+emotions: List[str]
+probabilities: List[float]
+predictions: List[int]
+processing_time: float
+model_info: Dict[str, Any]
}
class CombinedProcessingRequest {
+language: Optional[str]
+summary_max_length: Optional[int]
+emotion_threshold: Optional[float]
}
class CombinedProcessingResponse {
+transcription: TranscriptionResponse
+summary: SummarizationResponse
+emotions: EmotionDetectionResponse
+total_processing_time: float
+pipeline_steps: List[str]
}
class HealthResponse {
+status: str
+timestamp: datetime
+models_loaded: Dict[str, bool]
+memory_usage: Dict[str, float]
}
SAMOUnifiedAPIServer --> SummarizationRequest
SAMOUnifiedAPIServer --> SummarizationResponse
SAMOUnifiedAPIServer --> TranscriptionRequest
SAMOUnifiedAPIServer --> TranscriptionResponse
SAMOUnifiedAPIServer --> EmotionDetectionRequest
SAMOUnifiedAPIServer --> EmotionDetectionResponse
SAMOUnifiedAPIServer --> CombinedProcessingRequest
SAMOUnifiedAPIServer --> CombinedProcessingResponse
SAMOUnifiedAPIServer --> HealthResponse
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds DeBERTa deployment docs, CI/build and Cloud Run Dockerization, DeBERTa vs production model-loading switch, a unified FastAPI server (summarize/transcribe/detect/process), new 28-emotion labels and BERT classifier, consolidated YAML configs, many test/demo/monitoring scripts, and updated dependency and deployment tooling. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant API as SAMOUnifiedAPIServer
participant E as Emotion Detector
participant HF as HF Pipeline
rect rgba(200,220,255,0.12)
note right of API: Startup loads models (summarizer, transcriber, emotion)
end
Client->>API: POST /detect-emotions {text, threshold}
alt USE_DEBERTA = true
API->>E: _predict_emotions_deberta(text)
E-->>API: 28 emotions + confidences
else
API->>HF: pipeline.predict(text)
HF-->>API: production emotions + confidences
end
API-->>Client: emotions, probabilities, model_info
sequenceDiagram
autonumber
participant Dev
participant Build as Cloud Build / deploy_deberta.sh
participant AR as Artifact Registry
participant CloudRun
participant Container as DeBERTa Image
Dev->>Build: build & push image (Dockerfile.deberta)
Build->>AR: push image
AR-->>CloudRun: deploy new revision
CloudRun->>Container: start (env: USE_DEBERTA, PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION)
Container->>Container: (preloaded model or runtime load)
Container->>CloudRun: HEALTHCHECK /health -> 200
Dev->>Container: smoke tests (/detect-emotions, /admin/model_status)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
✨ 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. Comment |
|
Here's the code health analysis summary for commits Analysis Summary
|
There was a problem hiding this comment.
Summary of Changes
Hello @uelkerd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly upgrades the emotion detection capabilities by deploying a new DeBERTa-based model. The change enhances the granularity of emotion analysis, increasing the number of detectable emotions from 6 to 28, and improves overall accuracy. It also introduces a robust, unified API server to integrate this and other deep learning models, streamlining the deployment and management of AI services.
Highlights
- DeBERTa Model Integration: Deploys a new DeBERTa-based emotion detection API, replacing the previous model.
- Expanded Emotion Classes: Increases the number of detectable emotion classes from 6 to 28, offering significantly more granular sentiment analysis.
- Improved Accuracy: Achieves a higher F1 Macro score of 51.8% compared to the previous production model's ~45%.
- Production-Ready Deployment: Includes comprehensive security features (security headers, rate limiting, input validation) and is optimized for Google Cloud Run (AMD64, 4GB memory, auto-scaling).
- Unified API Server: Introduces a new FastAPI-based unified API server that integrates T5 summarization, Whisper transcription, and the new DeBERTa emotion detection model, providing individual and combined processing endpoints.
- Dependency and Configuration Updates: Major overhaul of API dependencies (requirements-api.txt) and introduction of detailed YAML configuration files for the unified API and emotion detection.
- Automated Deployment Script: Adds a deploy_deberta.sh script for streamlined Cloud Run deployment, including Docker image building, pushing, and service configuration.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Pull Request Overview
This PR deploys a production-ready DeBERTa emotion detection API to Google Cloud Run with 28 emotion classes, replacing the current production system that uses only 6 emotions. The deployment includes comprehensive security features, rate limiting, input validation, and API documentation.
Key changes:
- Integration of DeBERTa model with 51.8% F1 Macro score (vs previous 45%)
- Unified API server supporting T5 summarization, Whisper transcription, and BERT emotion detection
- Cloud Run deployment scripts with security hardening and Docker optimization
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_unified_api_server.py | Comprehensive test suite for unified API server endpoints |
| test_samo_emotion_detection_standalone.py | Standalone test for SAMO BERT emotion classifier functionality |
| src/models/unified_api_server.py | Main unified FastAPI server integrating all three models |
| src/models/emotion_detection/samo_bert_emotion_classifier.py | SAMO-enhanced BERT emotion classifier with temperature scaling |
| src/models/emotion_detection/emotion_labels.py | GoEmotions emotion categories and utility functions |
| scripts/start_api_server.py | Server startup script with configuration management |
| deployment/cloud-run/secure_api_server.py | Production Flask API with security features |
| deployment/cloud-run/deploy_deberta.sh | DeBERTa deployment script for Cloud Run |
| deployment/cloud-run/Dockerfile.deberta | DeBERTa-optimized Docker container |
| dependencies/requirements-api.txt | Updated API dependencies for unified server |
| configs/samo_emotion_detection_config.yaml | Configuration for emotion detection model |
| configs/samo_api_config.yaml | Unified API server configuration |
| DEBERTA_DEPLOYMENT_README.md | Deployment documentation and instructions |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:86` </location>
<code_context>
+ self.freeze_bert_layers = config["freeze_bert_layers"]
+ self.temperature = nn.Parameter(torch.ones(1) * config["temperature"])
+ self.class_weights = None
+ self.prediction_threshold = 0.6 # Updated from 0.5 to 0.6 based on calibration
+
+ # Load BERT model and tokenizer
</code_context>
<issue_to_address>
Hardcoded prediction threshold may reduce flexibility.
Allow the prediction threshold to be configurable via the config dictionary or as a class parameter to support different use cases without code changes.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
self.class_weights = None
self.prediction_threshold = 0.6 # Updated from 0.5 to 0.6 based on calibration
# Load BERT model and tokenizer
=======
self.class_weights = None
self.prediction_threshold = config.get("prediction_threshold", 0.6) # Configurable threshold, default 0.6
# Load BERT model and tokenizer
>>>>>>> REPLACE
</suggested_fix>
### Comment 2
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:84` </location>
<code_context>
+ self.hidden_dropout_prob = config["hidden_dropout_prob"]
+ self.classifier_dropout_prob = config["classifier_dropout_prob"]
+ self.freeze_bert_layers = config["freeze_bert_layers"]
+ self.temperature = nn.Parameter(torch.ones(1) * config["temperature"])
+ self.class_weights = None
+ self.prediction_threshold = 0.6 # Updated from 0.5 to 0.6 based on calibration
</code_context>
<issue_to_address>
Using nn.Parameter for temperature may unintentionally make it trainable.
If temperature should remain fixed, use a regular tensor or float. If it needs to be trainable, confirm it's included in the optimizer.
</issue_to_address>
### Comment 3
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:115` </location>
<code_context>
+ self._freeze_bert_layers(self.freeze_bert_layers)
+
+ # Set device
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ self.to(self.device)
+
</code_context>
<issue_to_address>
Device assignment may not respect user-specified device.
Check for a user-specified device in the config and use it if provided, rather than always defaulting to CUDA/CPU.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
# Set device
=======
# Set device
if hasattr(self, "config") and hasattr(self.config, "device") and self.config.device is not None:
self.device = torch.device(self.config.device)
else:
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.to(self.device)
>>>>>>> REPLACE
</suggested_fix>
### Comment 4
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:177` </location>
<code_context>
+ )
+
+ # Use [CLS] token representation for classification
+ pooled_output = bert_outputs.pooler_output
+
+ # Pass through classification head
</code_context>
<issue_to_address>
Using pooler_output may not be robust for all transformer models.
Some models may not return pooler_output or may set it to None. Add a check and fallback to the first token's hidden state if pooler_output is unavailable.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
# Use [CLS] token representation for classification
pooled_output = bert_outputs.pooler_output
# Pass through classification head
logits = self.classifier(pooled_output)
=======
# Use [CLS] token representation for classification
pooled_output = bert_outputs.pooler_output
if pooled_output is None:
# Fallback to first token's hidden state if pooler_output is unavailable
pooled_output = bert_outputs.last_hidden_state[:, 0, :]
# Pass through classification head
logits = self.classifier(pooled_output)
>>>>>>> REPLACE
</suggested_fix>
### Comment 5
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:256` </location>
<code_context>
+
+ # Get emotion names for predictions
+ for pred in batch_predictions:
+ emotions = [
+ f"emotion_{i}" for i, p in enumerate(pred) if p > 0
+ ]
+ all_emotions.append(emotions)
</code_context>
<issue_to_address>
Emotion labels are generic and not mapped to actual names.
Map prediction indices to descriptive emotion names using a label list or external mapping for better clarity.
Suggested implementation:
```python
# Define descriptive emotion labels
emotion_labels = [
"happy", "sad", "angry", "surprised", "fearful", "disgusted", "neutral"
] # Adjust this list to match your model's output dimension/order
# Get emotion names for predictions
for pred in batch_predictions:
emotions = [
emotion_labels[i] for i, p in enumerate(pred) if p > 0
]
all_emotions.append(emotions)
```
- Make sure the `emotion_labels` list matches the number and order of emotions your model predicts. Adjust the list as needed to fit your model's output.
- If `emotion_labels` is already defined elsewhere in your codebase, import and use it instead of redefining it here.
</issue_to_address>
### Comment 6
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:323` </location>
<code_context>
+ )
+
+ # Apply class weights if provided
+ if self.class_weights is not None:
+ bce_loss = bce_loss * self.class_weights.unsqueeze(0)
+
</code_context>
<issue_to_address>
Class weights are applied per batch, but shape alignment is not checked.
Verify that class_weights are properly broadcasted to bce_loss to prevent shape mismatches, especially if the number of classes changes.
</issue_to_address>
### Comment 7
<location> `src/models/emotion_detection/samo_bert_emotion_classifier.py:378` </location>
<code_context>
+ )
+
+ # Convert labels to tensor
+ label_tensor = torch.tensor(labels, dtype=torch.float)
+
+ return {
</code_context>
<issue_to_address>
No validation of label length against number of emotions.
Add a check to ensure the label list length matches the expected number of emotions to prevent runtime errors.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
# Convert labels to tensor
=======
# Validate label length
if len(labels) != self.num_emotions:
raise ValueError(
f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})."
)
# Convert labels to tensor
>>>>>>> REPLACE
</suggested_fix>
### Comment 8
<location> `src/models/unified_api_server.py:129` </location>
<code_context>
+ )
+
+ # Configure CORS
+ self.app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Configure for production
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+
</code_context>
<issue_to_address>
CORS is set to allow all origins, which may be risky for production.
Restrict allowed origins or make this setting configurable to reduce security risks in production.
Suggested implementation:
```python
import os
from fastapi.middleware.cors import CORSMiddleware
# Configure CORS
allowed_origins = os.getenv("API_ALLOWED_ORIGINS", "https://your-production-domain.com")
allowed_origins_list = [origin.strip() for origin in allowed_origins.split(",")]
```
```python
self.app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
- You should set the `API_ALLOWED_ORIGINS` environment variable in your deployment environment to a comma-separated list of allowed origins (e.g., `https://your-production-domain.com,https://another-domain.com`).
- Update documentation or deployment scripts to ensure this variable is set appropriately for production and development.
</issue_to_address>
### Comment 9
<location> `src/models/unified_api_server.py:225` </location>
<code_context>
+ raise HTTPException(status_code=503, detail="Transcription model not available")
+
+ # Validate file type
+ if not file.filename.lower().endswith(('.mp3', '.wav', '.m4a', '.ogg', '.flac')):
+ raise HTTPException(status_code=400, detail="Unsupported audio format")
+
</code_context>
<issue_to_address>
Audio file type validation is limited to file extension.
File extension checks can be easily circumvented. Please validate the file's MIME type or use a library to ensure the file is a valid audio format.
Suggested implementation:
```python
# Validate file type using MIME type
import magic
supported_mime_types = {
"audio/mpeg",
"audio/wav",
"audio/x-wav",
"audio/x-m4a",
"audio/mp4",
"audio/ogg",
"audio/flac",
"audio/x-flac",
}
file_content = await file.read()
mime_type = magic.from_buffer(file_content, mime=True)
await file.seek(0) # Reset file pointer for downstream use
if mime_type not in supported_mime_types:
raise HTTPException(status_code=400, detail=f"Unsupported audio format: {mime_type}")
```
- Ensure that `python-magic` is installed in your environment (`pip install python-magic`).
- If you have a global import section, move the `import magic` statement there for best practices.
- If you use the file object elsewhere, resetting the pointer with `await file.seek(0)` is necessary after reading its content.
- You may want to expand or adjust the `supported_mime_types` set based on the actual formats your transcription model supports.
</issue_to_address>
### Comment 10
<location> `src/models/unified_api_server.py:230` </location>
<code_context>
+
+ try:
+ # Save uploaded file temporarily
+ temp_path = f"/tmp/{file.filename}"
+ with open(temp_path, "wb") as buffer:
+ content = await file.read()
</code_context>
<issue_to_address>
Temporary file handling may be vulnerable to race conditions or collisions.
Generate a unique temporary filename for each upload to prevent collisions and security risks.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
try:
# Save uploaded file temporarily
temp_path = f"/tmp/{file.filename}"
with open(temp_path, "wb") as buffer:
content = await file.read()
buffer.write(content)
=======
import uuid
try:
# Save uploaded file temporarily with a unique name
unique_suffix = uuid.uuid4().hex
extension = file.filename.split('.')[-1] if '.' in file.filename else ''
temp_path = f"/tmp/{unique_suffix}.{extension}" if extension else f"/tmp/{unique_suffix}"
with open(temp_path, "wb") as buffer:
content = await file.read()
buffer.write(content)
>>>>>>> REPLACE
</suggested_fix>
### Comment 11
<location> `scripts/start_api_server.py:23` </location>
<code_context>
+ default="0.0.0.0",
+ help="Host to bind the server to (default: 0.0.0.0)"
+ )
+ parser.add_argument(
+ "--port",
+ type=int,
+ default=8000,
+ help="Port to bind the server to (default: 8000)"
+ )
+ parser.add_argument(
+ "--workers",
+ type=int,
+ default=1,
+ help="Number of worker processes (default: 1)"
+ )
+ parser.add_argument(
+ "--reload",
+ action="store_true",
+ help="Enable auto-reload for development"
+ )
+ parser.add_argument(
+ "--log-level",
+ default="info",
+ choices=["debug", "info", "warning", "error"],
+ help="Logging level (default: info)"
+ )
+ parser.add_argument(
+ "--config",
+ default="configs/samo_api_config.yaml",
+ help="Path to configuration file"
+ )
+
</code_context>
<issue_to_address>
Configuration file argument is accepted but not used.
Please implement logic to load and apply configuration from the provided --config file.
</issue_to_address>
### Comment 12
<location> `tests/test_unified_api_server.py:105` </location>
<code_context>
+ response = client.post("/summarize", json={"text": long_text})
+ assert response.status_code == 422
+
+ def test_detect_emotions_endpoint_success(self, client):
+ """Test successful emotion detection."""
+ test_text = "I am so happy today! This is amazing!"
+
+ request_data = {
+ "text": test_text,
+ "threshold": 0.5,
+ "top_k": 5
+ }
+
+ response = client.post("/detect-emotions", json=request_data)
+
+ assert response.status_code == 200
+ data = response.json()
+
+ assert "emotions" in data
+ assert "probabilities" in data
+ assert "predictions" in data
+ assert "processing_time" in data
+ assert "model_info" in data
+
+ assert isinstance(data["emotions"], list)
+ assert isinstance(data["probabilities"], list)
+ assert isinstance(data["predictions"], list)
+
+ def test_detect_emotions_endpoint_validation(self, client):
</code_context>
<issue_to_address>
No test for emotion detection with edge-case inputs (e.g., ambiguous or multi-emotion text).
Add tests for ambiguous or multi-emotion texts to verify the model and API handle these cases correctly.
</issue_to_address>
### Comment 13
<location> `tests/test_unified_api_server.py:130` </location>
<code_context>
+ assert isinstance(data["probabilities"], list)
+ assert isinstance(data["predictions"], list)
+
+ def test_detect_emotions_endpoint_validation(self, client):
+ """Test emotion detection endpoint validation."""
+ # Test empty text
+ response = client.post("/detect-emotions", json={"text": ""})
+ assert response.status_code == 422
+
+ # Test invalid threshold
</code_context>
<issue_to_address>
No test for emotion detection with maximum allowed text length.
Please add a test that submits a 10,000 character text to verify the endpoint handles large inputs correctly.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
def test_detect_emotions_endpoint_validation(self, client):
"""Test emotion detection endpoint validation."""
# Test empty text
response = client.post("/detect-emotions", json={"text": ""})
assert response.status_code == 422
# Test invalid threshold
response = client.post("/detect-emotions", json={
"text": "Test text",
"threshold": 1.5 # Invalid threshold
})
assert response.status_code == 422
=======
def test_detect_emotions_endpoint_validation(self, client):
"""Test emotion detection endpoint validation."""
# Test empty text
response = client.post("/detect-emotions", json={"text": ""})
assert response.status_code == 422
# Test invalid threshold
response = client.post("/detect-emotions", json={
"text": "Test text",
"threshold": 1.5 # Invalid threshold
})
assert response.status_code == 422
# Test maximum allowed text length (10,000 characters)
long_text = "a" * 10000
response = client.post("/detect-emotions", json={"text": long_text})
assert response.status_code == 200
data = response.get_json()
assert "emotions" in data
assert "probabilities" in data
assert "predictions" in data
assert "processing_time" in data
assert "model_info" in data
>>>>>>> REPLACE
</suggested_fix>
### Comment 14
<location> `tests/test_unified_api_server.py:143` </location>
<code_context>
+ })
+ assert response.status_code == 422
+
+ def test_transcribe_endpoint_validation(self, client):
+ """Test transcription endpoint validation."""
+ # Test without file
+ response = client.post("/transcribe")
+ assert response.status_code == 422
+
+ # Test with unsupported file type
+ file_content = b"fake audio content"
+ files = {"file": ("test.txt", file_content, "text/plain")}
+
+ response = client.post("/transcribe", files=files)
+ assert response.status_code == 400
+ assert "Unsupported audio format" in response.json()["detail"]
+
+ @patch('src.models.unified_api_server.create_whisper_transcriber')
</code_context>
<issue_to_address>
No test for valid audio file but empty content.
Please add a test for the case where a valid audio file is submitted with empty content to ensure the endpoint responds appropriately.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
def test_transcribe_endpoint_validation(self, client):
"""Test transcription endpoint validation."""
# Test without file
response = client.post("/transcribe")
assert response.status_code == 422
# Test with unsupported file type
file_content = b"fake audio content"
files = {"file": ("test.txt", file_content, "text/plain")}
response = client.post("/transcribe", files=files)
assert response.status_code == 400
assert "Unsupported audio format" in response.json()["detail"]
=======
def test_transcribe_endpoint_validation(self, client):
"""Test transcription endpoint validation."""
# Test without file
response = client.post("/transcribe")
assert response.status_code == 422
# Test with unsupported file type
file_content = b"fake audio content"
files = {"file": ("test.txt", file_content, "text/plain")}
response = client.post("/transcribe", files=files)
assert response.status_code == 400
assert "Unsupported audio format" in response.json()["detail"]
# Test with valid audio file type but empty content
empty_audio_content = b""
files = {"file": ("empty.wav", empty_audio_content, "audio/wav")}
response = client.post("/transcribe", files=files)
# Adjust the expected status code and error message as per your API's behavior
assert response.status_code in (400, 422)
assert "empty" in response.json()["detail"].lower() or "no audio" in response.json()["detail"].lower()
>>>>>>> REPLACE
</suggested_fix>
### Comment 15
<location> `test_samo_emotion_detection_standalone.py:50` </location>
<code_context>
+def test_emotion_predictions(model, all_emotions):
</code_context>
<issue_to_address>
Missing test for invalid input types and empty string in standalone emotion prediction.
Add tests for None, integer, list, and empty string inputs to verify the function handles unexpected input and raises errors appropriately.
</issue_to_address>
### Comment 16
<location> `test_samo_emotion_detection_standalone.py:169` </location>
<code_context>
+def test_performance():
</code_context>
<issue_to_address>
No test for performance under batch prediction with large input set.
Add a test that measures batch prediction speed and scalability with large input sets (e.g., 1000+ texts) to detect potential bottlenecks.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| except ValueError: | ||
| raise ValueError(f"Emotion '{emotion}' not found in GoEmotions list") |
There was a problem hiding this comment.
suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)
| except ValueError: | |
| raise ValueError(f"Emotion '{emotion}' not found in GoEmotions list") | |
| except ValueError as e: | |
| raise ValueError(f"Emotion '{emotion}' not found in GoEmotions list") from e |
| summary_response = SummarizationResponse( | ||
| summary=transcription_result.text[:200] + "...", | ||
| original_length=len(transcription_result.text), | ||
| summary_length=200, | ||
| processing_time=0.0, | ||
| model_info={"error": "Summarization model not available"} | ||
| ) |
There was a problem hiding this comment.
issue (code-quality): Use f-string instead of string concatenation (use-fstring-for-concatenation)
| server = SAMOUnifiedAPIServer() | ||
| return server |
There was a problem hiding this comment.
suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)
| server = SAMOUnifiedAPIServer() | |
| return server | |
| return SAMOUnifiedAPIServer() |
| assert "status" in data | ||
| assert "timestamp" in data | ||
| assert "models_loaded" in data | ||
| assert "memory_usage" in data | ||
|
|
||
| # Check models_loaded structure | ||
| models_loaded = data["models_loaded"] | ||
| assert "summarizer" in models_loaded | ||
| assert "transcriber" in models_loaded | ||
| assert "emotion_detector" in models_loaded |
There was a problem hiding this comment.
issue (code-quality): Extract duplicate code into method (extract-duplicate-method)
There was a problem hiding this comment.
Code Review
This pull request introduces a significant new feature by deploying a DeBERTa-based emotion detection API to Google Cloud Run. The changes are extensive, including new Dockerfiles, deployment scripts, API server implementations, model code, and tests. While the feature is valuable, the review has identified several critical and high-severity issues that need to be addressed. These include major security vulnerabilities such as hardcoded weak credentials and insecure file handling, configuration problems like incorrect model names and including development dependencies in production builds, and implementation bugs that affect API correctness and performance. Additionally, the PR appears to mix two separate API server implementations (one Flask-based, one FastAPI-based), which creates significant confusion and should be clarified or split into separate pull requests.
| python -c "from transformers import AutoTokenizer, AutoModelForSequenceClassification; \ | ||
| import os; \ | ||
| os.environ['USE_DEBERTA'] = 'true'; \ | ||
| os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'; \ | ||
| model_name='duelker/samo-goemotions-deberta-v3-large'; \ | ||
| print(f'Pre-downloading DeBERTa model {model_name}...'); \ | ||
| tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False, cache_dir='/app/models'); \ | ||
| model = AutoModelForSequenceClassification.from_pretrained(model_name, cache_dir='/app/models'); \ | ||
| print('DeBERTa model pre-downloaded successfully');" |
There was a problem hiding this comment.
The logic for pre-downloading the model is embedded in a long, multi-line python -c command within the Dockerfile. This is difficult to read, debug, and maintain. It's better practice to move this logic into a separate Python script (e.g., scripts/download_model.py), COPY it into the image, and then execute it with a RUN command. This improves modularity and readability.
| from pathlib import Path | ||
|
|
||
| # Add src to path for imports | ||
| sys.path.insert(0, str(Path(__file__).parent.parent / "src")) |
There was a problem hiding this comment.
Modifying sys.path manually is generally considered an anti-pattern in modern Python development as it can lead to brittle and hard-to-debug import issues. It would be more robust to structure this project as a proper Python package with a pyproject.toml or setup.py file. This allows for installing the package in editable mode (pip install -e .), which handles path resolution correctly.
|
|
||
| # Use exec form for CMD (Docker best practice) | ||
| # Set timeout to 0 for Cloud Run (allows unlimited request timeouts) | ||
| CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 --keep-alive 5 --max-requests 1000 --max-requests-jitter 100 --access-logfile - --error-logfile - --log-level info secure_api_server:app"] |
There was a problem hiding this comment.
The gunicorn server is configured with --workers 1 --threads 8. For a CPU-bound application running on a multi-core instance (2 CPUs are configured in deploy_deberta.sh), a single worker process may not fully utilize the available CPU resources due to Python's Global Interpreter Lock (GIL). It is generally recommended to use multiple worker processes. A common starting point is (2 * number_of_cores) + 1.
CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 5 --threads 2 --timeout 0 --keep-alive 5 --max-requests 1000 --max-requests-jitter 100 --access-logfile - --error-logfile - --log-level info secure_api_server:app"]
Resolved issues in the following files with DeepSource Autofix: 1. scripts/start_api_server.py 2. src/models/emotion_detection/emotion_labels.py 3. src/models/emotion_detection/samo_bert_emotion_classifier.py 4. src/models/unified_api_server.py 5. test_samo_emotion_detection_standalone.py 6. tests/test_unified_api_server.py
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
deployment/cloud-run/secure_api_server.py (2)
344-344: Fix typo in route path.The route decorator uses
/predict_batchbut should be/predict/batchto match the registered resource path.-@main_ns.route('/predict_batch') +@main_ns.route('/predict/batch') class PredictBatch(Resource):
421-421: Duplicate endpoint registration will cause routing conflicts.There are duplicate endpoint registrations for
/model_statusand/security_status. The decorator-based routes at lines 421 and 439 conflict with the resource registrations at lines 95-96.# Admin endpoints -@admin_ns.route('/model_status') +# Using resource registration instead of route decorator class ModelStatus(Resource):-@admin_ns.route('/security_status') +# Using resource registration instead of route decorator class SecurityStatus(Resource):Also applies to: 439-439
♻️ Duplicate comments (23)
configs/samo_api_config.yaml (1)
73-73: Security risk: API key authentication disabled by defaultSetting
api_keys_required: falseby default is a critical security vulnerability that could lead to unintentional exposure of the API in production. Authentication should be secure by default.Apply this diff to enable secure defaults:
- api_keys_required: false # Set to true for production + api_keys_required: true # Override with environment variable for developmentconfigs/samo_emotion_detection_config.yaml (1)
6-6: Model mismatch: Using BERT instead of DeBERTaThe configuration specifies
bert-base-uncasedwhile this PR is for deploying DeBERTa (duelker/samo-goemotions-deberta-v3-large). This inconsistency will cause the wrong model to be loaded.Apply this diff to align with the PR's DeBERTa deployment:
- name: "bert-base-uncased" # Robust BERT model for emotion understanding + name: "duelker/samo-goemotions-deberta-v3-large" # DeBERTa model for emotion understandingscripts/start_api_server.py (3)
15-15: Avoid manual sys.path manipulationModifying
sys.pathmanually can lead to brittle import resolution. Consider structuring the project as a proper Python package.
23-32: Configuration file is parsed but not usedThe
--configargument is accepted and logged but never actually loaded or applied to the server configuration.The configuration file should be loaded and passed to the server. Would you like me to generate the code to properly load and apply the YAML configuration?
Also applies to: 73-73, 84-84
84-84: Workers and reload arguments are not passed to server.run()The
--workersand--reloadarguments are parsed from command line but not passed to the server, causing them to be ignored.Apply this diff to pass the arguments:
- server.run(host=args.host, port=args.port) + server.run(host=args.host, port=args.port, workers=args.workers, reload=args.reload)However, note that the
SAMOUnifiedAPIServer.run()method insrc/models/unified_api_server.pydoesn't acceptworkersorreloadparameters, so you'll also need to update that method signature.dependencies/requirements-api.txt (1)
33-36: Development dependencies should be in a separate fileTesting dependencies like
pytest,pytest-asyncio,httpx, andpytest-mockshould not be included in production requirements as they increase image size and attack surface.Move development/testing dependencies to a separate
requirements-dev.txtfile:-# Development and testing -pytest>=7.4.0 -pytest-asyncio>=0.21.0 -httpx>=0.25.0 -pytest-mock>=3.12.0Create a new
dependencies/requirements-dev.txt:# Development and testing dependencies pytest>=7.4.0 pytest-asyncio>=0.21.0 httpx>=0.25.0 pytest-mock>=3.12.0deployment/cloud-run/Dockerfile.deberta (2)
80-80: Consider increasing worker count for better CPU utilizationWith 2 CPUs configured in the deployment script, a single worker process may not fully utilize available resources due to Python's GIL. Consider using multiple workers.
-CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 --keep-alive 5 --max-requests 1000 --max-requests-jitter 100 --access-logfile - --error-logfile - --log-level info secure_api_server:app"] +CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 3 --threads 3 --timeout 0 --keep-alive 5 --max-requests 1000 --max-requests-jitter 100 --access-logfile - --error-logfile - --log-level info secure_api_server:app"]
54-62: Extract model pre-download logic to a separate scriptThe multi-line Python command for pre-downloading the model is difficult to read and maintain. Move this to a separate Python script.
Create a new file
deployment/cloud-run/download_model.py:#!/usr/bin/env python3 """Pre-download DeBERTa model for faster container startup.""" import os from transformers import AutoTokenizer, AutoModelForSequenceClassification # Set environment variables os.environ['USE_DEBERTA'] = 'true' os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' model_name = 'duelker/samo-goemotions-deberta-v3-large' cache_dir = '/app/models' print(f'Pre-downloading DeBERTa model {model_name}...') tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False, cache_dir=cache_dir) model = AutoModelForSequenceClassification.from_pretrained(model_name, cache_dir=cache_dir) print('DeBERTa model pre-downloaded successfully')Then update the Dockerfile:
+# Copy model download script +COPY deployment/cloud-run/download_model.py . + # Pre-download the DeBERTa model during build to avoid OOM during startup RUN mkdir -p /app/models && \ - python -c "from transformers import AutoTokenizer, AutoModelForSequenceClassification; \ - import os; \ - os.environ['USE_DEBERTA'] = 'true'; \ - os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'; \ - model_name='duelker/samo-goemotions-deberta-v3-large'; \ - print(f'Pre-downloading DeBERTa model {model_name}...'); \ - tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False, cache_dir='/app/models'); \ - model = AutoModelForSequenceClassification.from_pretrained(model_name, cache_dir='/app/models'); \ - print('DeBERTa model pre-downloaded successfully');" + python download_model.pyDEBERTA_DEPLOYMENT_README.md (1)
42-44: Incorrect API endpoint in documentationThe documentation uses
/detect-emotionsbut the actual endpoint is/api/predictas shown in the deployment script.-curl -X POST http://localhost:8080/detect-emotions \ +curl -X POST http://localhost:8080/api/predict \ -H "Content-Type: application/json" \ -d '{"text": "I am feeling happy today!"}'deployment/cloud-run/deploy_deberta.sh (2)
37-37: Hardcoded project ID makes script non-portableThe hardcoded project ID default makes this script error-prone when used in different environments.
-PROJECT_ID="${PROJECT_ID:-the-tendril-466607-n8}" +PROJECT_ID="${PROJECT_ID?PROJECT_ID environment variable must be set}"
138-138: Critical security vulnerability: weak default API keyUsing
test123as a default API key is a critical security risk that could lead to unauthorized access in production.Apply this diff to require a secure API key:
- --set-env-vars="ADMIN_API_KEY=${ADMIN_API_KEY:-test123}" + --set-env-vars="ADMIN_API_KEY=${ADMIN_API_KEY?ADMIN_API_KEY environment variable must be set}"Also update lines 185, 197, and 210:
-MODEL_STATUS=$(curl -s "${SERVICE_URL}/admin/model_status" -H "X-API-Key: ${ADMIN_API_KEY:-test123}") +MODEL_STATUS=$(curl -s "${SERVICE_URL}/admin/model_status" -H "X-API-Key: ${ADMIN_API_KEY}")- -H "X-API-Key: ${ADMIN_API_KEY:-test123}" \ + -H "X-API-Key: ${ADMIN_API_KEY}" \Also applies to: 185-185, 197-197, 210-210
deployment/cloud-run/secure_api_server.py (1)
90-96: Fix undefined resource classes causing NameError on startup.The resource classes (
Health,Predict,PredictBatch,Emotions,ModelStatus,SecurityStatus) are being registered with the namespaces but are not defined in the visible code. This will causeNameErrorexceptions when the server starts.Based on the code structure and the related snippet from
test_minimal_swagger.py, these resources should be defined as classes before being registered. Move the class definitions (lines 266-461) above the registration block (lines 90-96):# Add namespaces to API api.add_namespace(main_ns) api.add_namespace(admin_ns) -# Register resources with namespaces -main_ns.add_resource(Health, '/health') -main_ns.add_resource(Predict, '/predict') -main_ns.add_resource(PredictBatch, '/predict/batch') -main_ns.add_resource(Emotions, '/emotions') -admin_ns.add_resource(ModelStatus, '/model/status') -admin_ns.add_resource(SecurityStatus, '/security/status') - # Define request/response models for SwaggerThen add the registration after the class definitions (after line 461):
# Register resources with namespaces after defining them main_ns.add_resource(Health, '/health') main_ns.add_resource(Predict, '/predict') main_ns.add_resource(PredictBatch, '/predict/batch') main_ns.add_resource(Emotions, '/emotions') admin_ns.add_resource(ModelStatus, '/model/status') admin_ns.add_resource(SecurityStatus, '/security/status')test_samo_emotion_detection_standalone.py (1)
65-83: Refactor loops in tests for better clarity.Loops in tests make them harder to understand and debug. Consider using parameterized tests or extracting the loop logic to helper functions.
Consider refactoring to use pytest's parametrize decorator or create a helper function:
def display_top_emotions(text, results, all_emotions, top_n=5): """Helper to display top emotion predictions.""" emotions = results['emotions'][0] probabilities = results['probabilities'][0] print(f" Detected emotions: {emotions}") # Show top probabilities top_indices = sorted(range(len(probabilities)), key=lambda i: probabilities[i], reverse=True)[:top_n] print(" Top probabilities:") for idx in top_indices: emotion_name = all_emotions[idx] prob = probabilities[idx] print(f" {emotion_name}: {prob:.3f}") # Then in the test: for i, text in enumerate(test_texts, 1): print(f"\n Text {i}: {text}") results = model.predict_emotions(text, threshold=0.3, top_k=3) display_top_emotions(text, results, all_emotions)src/models/unified_api_server.py (5)
347-353: Use f-string for better readability.String concatenation is less readable than f-strings.
else: summary_response = SummarizationResponse( - summary=transcription_result.text[:200] + "...", + summary=f"{transcription_result.text[:200]}...", original_length=len(transcription_result.text), summary_length=200, processing_time=0.0, model_info={"error": "Summarization model not available"} )
130-130: CORS allowing all origins is a security risk in production.Using
allow_origins=["*"]allows any website to make requests to your API, which can lead to CSRF attacks and data leakage.Configure CORS properly for production by loading allowed origins from environment variables:
+ import os + + # Configure CORS based on environment + allowed_origins = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000").split(",") + # Configure CORS self.app.add_middleware( CORSMiddleware, - allow_origins=["*"], # Configure for production + allow_origins=allowed_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
224-224: File validation based on extension alone is insufficient.File extensions can be easily spoofed. Validate the actual content type to ensure security.
The current validation only checks the file extension which can be bypassed. Would you like me to implement proper MIME type validation using python-magic or by checking the file's magic bytes?
# Example implementation with python-magic: import magic # Validate file type using MIME type file_content = await file.read() mime = magic.Magic(mime=True) mime_type = mime.from_buffer(file_content) await file.seek(0) # Reset for later use ALLOWED_MIME_TYPES = { 'audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/x-m4a', 'audio/mp4', 'audio/ogg', 'audio/flac' } if mime_type not in ALLOWED_MIME_TYPES: raise HTTPException(status_code=400, detail=f"Unsupported audio format: {mime_type}")
229-229: Path traversal vulnerability in file upload handling.Using user-provided filenames directly in file paths is a critical security vulnerability. An attacker could provide malicious filenames like
../../etc/passwd.Generate unique, safe filenames instead:
+ import uuid + try: # Save uploaded file temporarily - temp_path = f"/tmp/{file.filename}" + # Generate safe filename with original extension + file_ext = Path(file.filename).suffix if file.filename else '' + safe_filename = f"{uuid.uuid4().hex}{file_ext}" + temp_path = f"/tmp/{safe_filename}" with open(temp_path, "wb") as buffer: content = await file.read() buffer.write(content)Apply the same fix to line 310 in the
process_audio_completelyfunction.Also applies to: 310-310
423-426: Method signature doesn't accept workers and reload parameters.The
runmethod signature doesn't includeworkersandreloadparameters that are being passed fromstart_api_server.py.- def run(self, host: str = "0.0.0.0", port: int = 8000): + def run(self, host: str = "0.0.0.0", port: int = 8000, workers: int = 1, reload: bool = False): """Run the API server.""" - logger.info(f"Starting SAMO Unified API Server on {host}:{port}") - uvicorn.run(self.app, host=host, port=port) + logger.info(f"Starting SAMO Unified API Server on {host}:{port} with {workers} worker(s)") + uvicorn.run(self.app, host=host, port=port, workers=workers, reload=reload)src/models/emotion_detection/emotion_labels.py (1)
182-183: Chain exception for better error context.When re-raising exceptions, use
fromto preserve the original exception context.try: return GOEMOTIONS_EMOTIONS.index(emotion.lower()) - except ValueError: - raise ValueError(f"Emotion '{emotion}' not found in GoEmotions list") + except ValueError as e: + raise ValueError(f"Emotion '{emotion}' not found in GoEmotions list") from esrc/models/emotion_detection/samo_bert_emotion_classifier.py (4)
83-83: Temperature as nn.Parameter may be unintentionally trainable.Using
nn.Parametermakes the temperature trainable during backpropagation. Confirm if this is intended behavior.If temperature should be fixed during training, consider using a regular tensor:
- self.temperature = nn.Parameter(torch.ones(1) * config["temperature"]) + self.temperature = torch.tensor([config["temperature"]], device=self.device)If it should be trainable, ensure it's included in the optimizer's parameter list and document this behavior.
84-86: Make prediction threshold configurable.The prediction threshold is hardcoded to 0.6 which reduces flexibility for different use cases.
Allow configuration via the config dictionary:
self.temperature = nn.Parameter(torch.ones(1) * config["temperature"]) self.class_weights = None - self.prediction_threshold = 0.6 # Updated from 0.5 to 0.6 based on calibration + self.prediction_threshold = config.get("prediction_threshold", 0.6) # Configurable with default
322-323: Verify class weights broadcasting for shape alignment.Class weights are being applied but shape alignment is not verified, which could cause runtime errors if dimensions mismatch.
Add shape validation to prevent runtime errors:
# Apply class weights if provided if self.class_weights is not None: + # Ensure class weights match the number of classes + if self.class_weights.shape[0] != targets.shape[1]: + raise ValueError(f"Class weights shape {self.class_weights.shape} doesn't match targets shape {targets.shape}") bce_loss = bce_loss * self.class_weights.unsqueeze(0)
255-257: Emotion predictions return generic labels instead of meaningful names.The function returns generic labels like
emotion_0,emotion_1which is not user-friendly. Use actual emotion names from the emotion_labels module.Import and use the emotion name mapping:
+from .emotion_labels import get_emotion_name + # Get emotion names for predictions for pred in batch_predictions: emotions = [ - f"emotion_{i}" for i, p in enumerate(pred) if p > 0 + get_emotion_name(i) for i, p in enumerate(pred) if p > 0 ] all_emotions.append(emotions)
🧹 Nitpick comments (12)
configs/samo_api_config.yaml (1)
86-86: Add newline at end of fileYAML files should end with a newline character for proper POSIX compliance.
reload_on_change: false +configs/samo_emotion_detection_config.yaml (1)
13-187: Remove trailing spaces throughout the fileThe file contains trailing spaces on 32 lines which should be removed for cleaner code.
Run the following command to remove all trailing spaces:
sed -i 's/[[:space:]]*$//' configs/samo_emotion_detection_config.yamlscripts/start_api_server.py (2)
88-90: Use logging.exception for better error tracingWhen catching exceptions, use
logging.exceptioninstead oflogging.errorto include the full traceback.except Exception as e: - logger.error(f"❌ Failed to start server: {e}") + logger.exception("❌ Failed to start server") sys.exit(1)
1-1: Make the script executableThe shebang is present but the file is not executable. The script should have execute permissions.
Run this command to make the script executable:
chmod +x scripts/start_api_server.pydependencies/requirements-api.txt (1)
5-7: Consider using compatible version specifiers instead of exact pinsUsing exact version pins can cause dependency resolution conflicts. Consider using compatible release specifiers for better flexibility.
-fastapi==0.104.1 -uvicorn[standard]==0.24.0 -pydantic==2.5.0 +fastapi~=0.104.1 +uvicorn[standard]~=0.24.0 +pydantic~=2.5.0tests/test_unified_api_server.py (3)
88-101: Add test for maximum text length validationThe test validates empty and too-long text but doesn't test the boundary case of maximum allowed text length.
Add a test case for the maximum allowed length:
@staticmethod def test_summarize_endpoint_validation(client): """Test summarization endpoint validation.""" # Test empty text response = client.post("/summarize", json={"text": ""}) assert response.status_code == 422 # Validation error # Test too short text response = client.post("/summarize", json={"text": "Hi"}) assert response.status_code == 422 # Test too long text long_text = "word " * 10000 response = client.post("/summarize", json={"text": long_text}) assert response.status_code == 422 + + # Test maximum allowed text (should succeed) + max_text = "a" * 10000 # Adjust based on actual max length + response = client.post("/summarize", json={"text": max_text}) + assert response.status_code == 200
143-156: Add test for empty audio file contentConsider adding a test case for valid audio file types with empty content to ensure proper error handling.
@staticmethod def test_transcribe_endpoint_validation(client): """Test transcription endpoint validation.""" # Test without file response = client.post("/transcribe") assert response.status_code == 422 # Test with unsupported file type file_content = b"fake audio content" files = {"file": ("test.txt", file_content, "text/plain")} response = client.post("/transcribe", files=files) assert response.status_code == 400 assert "Unsupported audio format" in response.json()["detail"] + + # Test with valid file type but empty content + empty_content = b"" + files = {"file": ("empty.wav", empty_content, "audio/wav")} + response = client.post("/transcribe", files=files) + # The API might fail during transcription + assert response.status_code in (400, 500)
1-1: Make test file executableThe shebang is present but the file is not marked as executable.
chmod +x tests/test_unified_api_server.pydeployment/cloud-run/secure_api_server.py (1)
139-141: Environment variable configuration for DeBERTa is redundant.The DeBERTa configuration environment variables are being set but the model loading uses
model_utilswhich should handle this internally. Additionally, the comment mentions DeBERTa but the actual model being loaded appears to be BERT-based.Consider removing these lines if
model_utilsalready handles the configuration, or clarify why DeBERTa-specific settings are needed for a BERT-based model:-# DeBERTa configuration - set environment variables for proper model loading -os.environ.setdefault('USE_DEBERTA', 'true') -os.environ.setdefault('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', 'python') -logger.info(f"🔧 DeBERTa configuration: USE_DEBERTA={os.environ.get('USE_DEBERTA')}, PROTOCOL_BUFFERS={os.environ.get('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION')}") +# Model configuration is handled by model_utilstest_samo_emotion_detection_standalone.py (1)
183-197: Performance test needs proper benchmarking methodology.The performance test uses
time.time()which includes system scheduling overhead and is not suitable for accurate micro-benchmarks. Consider usingtimeitorperf_counterfor more accurate measurements.- import time - start_time = time.time() + import time + # Use perf_counter for more accurate timing + start_time = time.perf_counter() results = model.predict_emotions(text, threshold=0.3) - end_time = time.time() + end_time = time.perf_counter()Also consider:
- Running multiple iterations and reporting statistics (mean, std, min, max)
- Warming up the model with a few predictions before timing
- Testing with batch processing to measure throughput
src/models/emotion_detection/emotion_labels.py (1)
1-1: Shebang present but file not executable.The file has a shebang line but is not marked as executable. Either remove the shebang or make the file executable.
Either remove the shebang:
-#!/usr/bin/env python3 """Or make the file executable in your deployment process:
chmod +x src/models/emotion_detection/emotion_labels.pysrc/models/emotion_detection/samo_bert_emotion_classifier.py (1)
189-189: Use explicit Optional type annotation.PEP 484 prohibits implicit Optional. Be explicit about optional parameters.
def predict_emotions( self, texts: Union[str, List[str]], - threshold: float = None, + threshold: Optional[float] = None, top_k: Optional[int] = None, batch_size: int = 32, ) -> Dict[str, Union[List[str], List[float], List[List[int]]]]:
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
DEBERTA_DEPLOYMENT_README.md(1 hunks)configs/samo_api_config.yaml(1 hunks)configs/samo_emotion_detection_config.yaml(1 hunks)dependencies/requirements-api.txt(1 hunks)deployment/cloud-run/Dockerfile.deberta(1 hunks)deployment/cloud-run/deploy_deberta.sh(1 hunks)deployment/cloud-run/secure_api_server.py(2 hunks)scripts/start_api_server.py(1 hunks)src/models/emotion_detection/emotion_labels.py(1 hunks)src/models/emotion_detection/samo_bert_emotion_classifier.py(1 hunks)src/models/unified_api_server.py(1 hunks)test_samo_emotion_detection_standalone.py(1 hunks)tests/test_unified_api_server.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/models/unified_api_server.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(386-426)predict_emotions(186-267)
tests/test_unified_api_server.py (2)
src/models/unified_api_server.py (1)
SAMOUnifiedAPIServer(114-426)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(186-267)
test_samo_emotion_detection_standalone.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (5)
create_samo_bert_emotion_classifier(386-426)count_parameters(274-276)count_frozen_parameters(278-280)predict_emotions(186-267)set_temperature(269-272)src/models/emotion_detection/emotion_labels.py (2)
get_all_emotions(269-276)get_emotion_description(243-253)
deployment/cloud-run/secure_api_server.py (1)
deployment/cloud-run/test_minimal_swagger.py (2)
Health(33-35)get(34-35)
scripts/start_api_server.py (1)
src/models/unified_api_server.py (2)
SAMOUnifiedAPIServer(114-426)run(423-426)
🪛 YAMLlint (1.37.1)
configs/samo_api_config.yaml
[error] 86-86: no new line character at the end of file
(new-line-at-end-of-file)
configs/samo_emotion_detection_config.yaml
[error] 13-13: trailing spaces
(trailing-spaces)
[error] 16-16: trailing spaces
(trailing-spaces)
[error] 19-19: trailing spaces
(trailing-spaces)
[error] 28-28: trailing spaces
(trailing-spaces)
[error] 31-31: trailing spaces
(trailing-spaces)
[error] 34-34: trailing spaces
(trailing-spaces)
[error] 43-43: trailing spaces
(trailing-spaces)
[error] 47-47: trailing spaces
(trailing-spaces)
[error] 51-51: trailing spaces
(trailing-spaces)
[error] 55-55: trailing spaces
(trailing-spaces)
[error] 66-66: trailing spaces
(trailing-spaces)
[error] 69-69: trailing spaces
(trailing-spaces)
[error] 83-83: trailing spaces
(trailing-spaces)
[error] 86-86: trailing spaces
(trailing-spaces)
[error] 96-96: trailing spaces
(trailing-spaces)
[error] 105-105: trailing spaces
(trailing-spaces)
[error] 108-108: trailing spaces
(trailing-spaces)
[error] 117-117: trailing spaces
(trailing-spaces)
[error] 121-121: trailing spaces
(trailing-spaces)
[error] 124-124: trailing spaces
(trailing-spaces)
[error] 132-132: trailing spaces
(trailing-spaces)
[error] 135-135: trailing spaces
(trailing-spaces)
[error] 138-138: trailing spaces
(trailing-spaces)
[error] 141-141: trailing spaces
(trailing-spaces)
[error] 150-150: trailing spaces
(trailing-spaces)
[error] 154-154: trailing spaces
(trailing-spaces)
[error] 163-163: trailing spaces
(trailing-spaces)
[error] 166-166: trailing spaces
(trailing-spaces)
[error] 169-169: trailing spaces
(trailing-spaces)
[error] 177-177: trailing spaces
(trailing-spaces)
[error] 180-180: trailing spaces
(trailing-spaces)
[error] 183-183: trailing spaces
(trailing-spaces)
🪛 Ruff (0.12.2)
src/models/emotion_detection/samo_bert_emotion_classifier.py
1-1: Shebang is present but file is not executable
(EXE001)
189-189: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
src/models/unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
152-152: Do not catch blind exception: Exception
(BLE001)
153-153: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
161-161: Do not catch blind exception: Exception
(BLE001)
162-162: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
171-171: Do not catch blind exception: Exception
(BLE001)
172-172: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
208-208: Do not catch blind exception: Exception
(BLE001)
209-209: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
210-210: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
210-210: Use explicit conversion flag
Replace with conversion flag
(RUF010)
215-215: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
229-229: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
256-256: Do not catch blind exception: Exception
(BLE001)
257-257: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
258-258: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
258-258: Use explicit conversion flag
Replace with conversion flag
(RUF010)
288-288: Do not catch blind exception: Exception
(BLE001)
289-289: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
290-290: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
290-290: Use explicit conversion flag
Replace with conversion flag
(RUF010)
295-295: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
308-308: Abstract raise to an inner function
(TRY301)
310-310: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
396-396: Do not catch blind exception: Exception
(BLE001)
397-397: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
398-398: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
398-398: Use explicit conversion flag
Replace with conversion flag
(RUF010)
423-423: Possible binding to all interfaces
(S104)
src/models/emotion_detection/emotion_labels.py
1-1: Shebang is present but file is not executable
(EXE001)
183-183: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
183-183: Avoid specifying long messages outside the exception class
(TRY003)
201-201: Avoid specifying long messages outside the exception class
(TRY003)
tests/test_unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
test_samo_emotion_detection_standalone.py
1-1: Shebang is present but file is not executable
(EXE001)
199-199: Do not catch blind exception: Exception
(BLE001)
deployment/cloud-run/secure_api_server.py
91-91: Undefined name Health
(F821)
92-92: Undefined name Predict
(F821)
93-93: Undefined name PredictBatch
(F821)
94-94: Undefined name Emotions
(F821)
95-95: Undefined name ModelStatus
(F821)
96-96: Undefined name SecurityStatus
(F821)
scripts/start_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
25-25: Possible binding to all interfaces
(S104)
88-88: Do not catch blind exception: Exception
(BLE001)
89-89: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
- Fixed 6 critical PYL-E0601 errors in secure_api_server.py - Moved resource registration after class definitions - Fixed batch prediction route path mismatch - Made prediction threshold configurable - Added temperature parameter trainability control - Improved device assignment with config support - Added pooler_output fallback for transformer models - Enhanced emotion labels with descriptive names - Added comprehensive input validation - Improved CORS security configuration - Enhanced audio file validation with MIME types - Fixed temporary file handling security - Added configuration file loading support - Added comprehensive edge-case tests - Fixed all 16 code review comments
- Kept both edge case tests and validation tests - Fixed method decorators to be consistent - Maintained all new test functionality
- Fixed 26 FLK-E501 line length violations in unified_api_server.py - Broke long lines into multiple lines for better readability - Fixed long import statements and model field definitions - Improved code formatting and maintainability - All critical linting errors resolved
Resolved issues in the following files with DeepSource Autofix: 1. deployment/cloud-run/model_utils.py 2. scripts/deployment/deploy_deberta_model.py 3. scripts/start_api_server.py 4. scripts/testing/cloud_run_deployment_monitor.py 5. scripts/testing/comprehensive_journal_inference_demo.py 6. scripts/testing/deberta_journal_inference_demo.py 7. scripts/testing/deberta_safetensors_test.py 8. scripts/testing/deberta_simple_test.py 9. scripts/testing/deberta_workaround.py 10. scripts/testing/model_comparison_test.py 11. scripts/testing/test_deberta_api.py 12. src/models/emotion_detection/samo_bert_emotion_classifier.py 13. tests/test_unified_api_server.py
- Fixed test_model_unavailable_errors to use api_server fixture instead of client.app.state - Changed from @staticmethod to instance method to access api_server.models - Used deep copy for proper model backup and restoration - Fixed incorrect model mocking approach that was trying to access non-existent app.state.models
…--DL into feat/dl-deberta-integration
- Remove API key exposure from deployment script output - Add documentation for proper PYTHONPATH usage in test file - Verify health check endpoint path is correct (/api/health) - Improve security by redacting sensitive information in logs
CRITICAL SECURITY FIXES: - Set api_keys_required to true by default (was false - major security risk) - Remove weak default API key 'test123' and require secure ADMIN_API_KEY - Remove hardcoded project ID and require PROJECT_ID environment variable - Remove dev dependencies from production requirements-api.txt CONFIGURATION FIXES: - Update emotion detection config to use correct DeBERTa model name - Fix server.run method to accept workers and reload parameters - Pass workers and reload arguments from startup script - Fix documentation endpoint inconsistency (/detect-emotions -> /api/predict) SECURITY IMPROVEMENTS: - All sensitive defaults now require explicit environment variables - Production deployments will fail if secure keys not provided - Dev dependencies properly separated from production builds - Configuration consistency across all files
…mports - Fix Dockerfile health check to use correct /health endpoint (not /api/health) - Replace fragile sys.path manipulation with proper package imports - Add comprehensive error handling and documentation for test file - Update project status to 95% complete with remaining optimization tasks
There was a problem hiding this comment.
Actionable comments posted: 38
♻️ Duplicate comments (12)
deployment/cloud-run/secure_api_server.py (1)
455-461: Verify resource classes are properly imported before registration.The resource classes (Health, Predict, PredictBatch, Emotions, ModelStatus, SecurityStatus) are being registered but are not imported. This will cause NameError exceptions when the server starts.
scripts/start_api_server.py (1)
106-106: Workers and reload arguments not passed to server.run()The
--workersand--reloadarguments are parsed but not utilized when starting the server.Pass the parsed arguments to the server:
- server.run(host=args.host, port=args.port) + # The SAMOUnifiedAPIServer.run() method needs to be updated to support these parameters + # For now, log a warning if non-default values are used + if args.workers != 1 or args.reload: + logger.warning("⚠️ --workers and --reload options are not yet implemented in SAMOUnifiedAPIServer") + server.run(host=args.host, port=args.port)tests/test_unified_api_server.py (5)
21-31: Inject mocks before server routes are bound (fix fixture or set models explicitly).The server loads models in init and binds routes immediately. Function-level @patch in tests runs after the fixture creates the server, so your patches don’t affect already-loaded (or failed/None) models. This yields 503s or real downloads.
Prefer setting api_server.models directly per test or patching creators inside the fixture before instantiation.
Apply one of these diffs (Option A recommended):
Option A: Set lightweight defaults in the fixture, avoid heavy loads.
@@ @pytest.fixture def api_server(self): """Create API server instance for testing.""" - server = SAMOUnifiedAPIServer() - return server + server = SAMOUnifiedAPIServer() + # Override heavy model loads with light stubs by default + from unittest.mock import Mock + summarizer = Mock() + summarizer.generate_summary.return_value = "stub summary" + summarizer.get_model_info.return_value = {"model_name": "t5-small"} + server.models = { + "summarizer": summarizer, # default available + "transcriber": None, # set per test + "emotion_detector": None # set per test + } + return serverOption B: Patch creators inside the fixture (ensures mocks are used during init).
@@ def api_server(self): """Create API server instance for testing.""" - server = SAMOUnifiedAPIServer() - return server + from unittest.mock import Mock, patch + with patch('src.models.unified_api_server.create_t5_summarizer') as mk_sum, \ + patch('src.models.unified_api_server.create_whisper_transcriber') as mk_tr, \ + patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') as mk_em: + summarizer = Mock() + summarizer.generate_summary.return_value = "stub summary" + summarizer.get_model_info.return_value = {"model_name": "t5-small"} + mk_sum.return_value = summarizer + mk_tr.return_value = None + mk_em.return_value = (None, None) + server = SAMOUnifiedAPIServer() + return server
213-250: Transcription success: patch MIME detection and inject transcriber stub.python-magic inspects bytes; “fake mp3 content” won’t be detected as audio/mpeg. Patch magic.from_buffer and set the transcriber on api_server.
- @patch('src.models.unified_api_server.create_whisper_transcriber') - def test_transcribe_endpoint_success(self, mock_create_transcriber, client): + def test_transcribe_endpoint_success(self, api_server, client): @@ - mock_transcriber.transcribe.return_value = mock_result - mock_create_transcriber.return_value = mock_transcriber + mock_transcriber.transcribe.return_value = mock_result + api_server.models["transcriber"] = mock_transcriber @@ - response = client.post("/transcribe", files=files) + from unittest.mock import patch + with patch('magic.from_buffer', return_value="audio/mpeg"): + response = client.post("/transcribe", files=files)
52-85: Make summarization test deterministic by injecting the stub summarizer.The test currently relies on whatever the fixture loaded; ensure the summarizer is present and mocked.
- @staticmethod - def test_summarize_endpoint_success(client): + def test_summarize_endpoint_success(self, api_server, client): @@ - response = client.post("/summarize", json=request_data) + # Ensure summarizer is available + from unittest.mock import Mock + summarizer = Mock() + summarizer.generate_summary.return_value = "Today was a rollercoaster... handled it well." + summarizer.get_model_info.return_value = {"model_name": "t5-small"} + api_server.models["summarizer"] = summarizer + + response = client.post("/summarize", json=request_data)
103-127: Emotion success test needs a mocked model on the server instance.Without setting api_server.models["emotion_detector"], the endpoint returns 503. Provide a stub that returns the expected structure.
- @staticmethod - def test_detect_emotions_endpoint_success(client): + def test_detect_emotions_endpoint_success(self, api_server, client): @@ - response = client.post("/detect-emotions", json=request_data) + # Inject stub emotion detector + from unittest.mock import Mock + emo = Mock() + emo.device = "cpu" + emo.predict_emotions.return_value = { + "emotions": [["joy", "excitement"]], + "probabilities": [[0.91, 0.77]], + "predictions": [[1, 1]] + } + api_server.models["emotion_detector"] = emo + + response = client.post("/detect-emotions", json=request_data)
258-327: Combined pipeline: stop patching creators; set server models and patch MIME.Current @patch targets still suffer from “patch after init.” Set api_server.models instead and patch magic for the upload.
- @patch('src.models.unified_api_server.create_whisper_transcriber') - @patch('src.models.unified_api_server.create_t5_summarizer') - @patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') - def test_combined_processing_success(self, mock_emotion_detector, mock_summarizer, - mock_transcriber, client): + def test_combined_processing_success(self, api_server, client): @@ - mock_transcriber.return_value.transcribe.return_value = mock_transcription + from unittest.mock import Mock, patch + mock_transcriber = Mock() + mock_transcriber.transcribe.return_value = mock_transcription @@ - mock_summarizer.return_value = mock_summary_model + # Assign models directly to the server + api_server.models["transcriber"] = mock_transcriber + api_server.models["summarizer"] = mock_summary_model @@ - mock_emotion_detector.return_value = mock_emotion_model + api_server.models["emotion_detector"] = mock_emotion_model @@ - response = client.post("/process-audio", files=files) + with patch('magic.from_buffer', return_value="audio/mpeg"): + response = client.post("/process-audio", files=files)src/models/unified_api_server.py (1)
369-376: NameError: uuid is not imported in /process-audio.process_audio_completely uses uuid.uuid4() but doesn’t import uuid in this scope. This will crash at runtime.
@@ - @self.app.post("/process-audio", response_model=CombinedProcessingResponse) + @self.app.post("/process-audio", response_model=CombinedProcessingResponse) async def process_audio_completely( @@ - """Complete pipeline: Audio -> Transcription -> Summary -> Emotion Analysis.""" + """Complete pipeline: Audio -> Transcription -> Summary -> Emotion Analysis.""" + import uuid # ensure uuid is available in this scopeAlternatively, add a single top-level
import uuidnear other imports.src/models/emotion_detection/samo_bert_emotion_classifier.py (4)
86-95: Nice: trainable temperature and configurable threshold added.Addresses earlier feedback about temperature and threshold configurability. Looks good.
122-127: Nice: device can be user-specified via config.Resolves prior concern about always defaulting to CUDA/CPU.
187-193: Robust fallback for missing pooler_output.Good defensive handling for models without a pooler.
268-275: Don’t hardcode labels; use the shared emotion_labels mapping (keeps API contract consistent).Duplicate lists drift from src/models/emotion_detection/emotion_labels.py and Cloud Run EMOTION_MAPPING.
- # Define descriptive emotion labels (28 emotions) - emotion_labels = [ - "admiration", "amusement", "anger", "annoyance", "approval", "caring", - "confusion", "curiosity", "desire", "disappointment", "disapproval", - "disgust", "embarrassment", "excitement", "fear", "gratitude", "grief", - "joy", "love", "nervousness", "optimism", "pride", "realization", - "relief", "remorse", "sadness", "surprise", "neutral" - ] + # Use shared mapping; prefer model config id2label if present. + label_lookup = getattr(self.config, "id2label", None) + if isinstance(label_lookup, dict): + def _label(i: int) -> str: + return label_lookup.get(str(i)) or label_lookup.get(i) or f"emotion_{i}" + else: + from .emotion_labels import GOEMOTIONS_EMOTIONS as EMOTION_LABELS + def _label(i: int) -> str: + return EMOTION_LABELS[i] if i < len(EMOTION_LABELS) else f"emotion_{i}"And map predictions:
- for pred in batch_predictions: - emotions = [ - emotion_labels[i] for i, p in enumerate(pred) if p > 0 and i < len(emotion_labels) - ] + for pred in batch_predictions: + emotions = [_label(i) for i, p in enumerate(pred) if p > 0] all_emotions.append(emotions)
🧹 Nitpick comments (44)
deployment/cloud-run/cloudbuild.yaml (1)
3-5: Tag builds by commit and force amd64 platformAdd a versioned tag and platform to avoid “latest” drift and ensure the intended arch for Cloud Run.
- args: ['build', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta', '-f', 'deployment/cloud-run/Dockerfile.deberta', '.'] + args: ['build', '--platform=linux/amd64', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$COMMIT_SHA', '-f', 'deployment/cloud-run/Dockerfile.deberta', '.'] @@ - - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta' + - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$COMMIT_SHA'scripts/testing/test_deberta_api.py (2)
64-73: Use dict.get for safer access (and quiet Ruff RUF019)Minor cleanup to avoid redundant key checks.
- if 'emotions' in result and result['emotions']: - top_emotions = result['emotions'][:3] + emotions = result.get('emotions') or [] + if emotions: + top_emotions = emotions[:3] @@ - if 'emotions' in result and result['emotions']: - top_emotion = result['emotions'][0] + emotions = result.get('emotions') or [] + if emotions: + top_emotion = emotions[0]Also applies to: 141-144
38-40: Log full tracebacks for failuresUse logger.exception for richer diagnostics; keep broad excepts if desirable in a test harness.
- except Exception as e: - logger.error(f"❌ API health check error: {e}") + except Exception: + logger.exception("❌ API health check error") @@ - except Exception as e: - logger.error(f"❌ Emotion prediction error: {e}") + except Exception: + logger.exception("❌ Emotion prediction error") @@ - except Exception as e: - logger.error(f"❌ Model status error: {e}") + except Exception: + logger.exception("❌ Model status error") @@ - except Exception as e: - logger.error(f"❌ Batch prediction error: {e}") + except Exception: + logger.exception("❌ Batch prediction error")Also applies to: 79-81, 108-110, 150-152
scripts/testing/deberta_safetensors_test.py (5)
47-49: Print actual load time instead of literal placeholder- load_time = time.time() - start_time - print(".2f") + load_time = time.time() - start_time + print(f"✅ Model loaded in {load_time:.2f}s")
97-101: Report meaningful performance metrics- print(".3f") - print(".1f") - print(".3f") + print(f"⏱️ Total time: {total_time:.3f}s") + print(f"⏱️ Avg per text: {avg_time:.1f}s") + print(f"🚀 Throughput: {len(test_texts)/total_time:.3f} req/s")
103-107: Show top label and score for samples- for i, (text, result) in enumerate(zip(test_texts[:3], results[:3])): - top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} + for i, (text, result) in enumerate(zip(test_texts[:3], results[:3])): + top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} print(f"Text: {text[:50]}...") - print(".3f") + print(f" Top: {top_emotion['label']} ({top_emotion['score']:.3f})") print()
129-137: Fill in comparison details and list top-5 properly- print("📊 Comparison Results:") - print(f" DeBERTa: {deberta_top['label']} ({deberta_top['score']:.3f})") - print(".2f") + print("📊 Comparison Results:") + print(f" DeBERTa: {deberta_top['label']} ({deberta_top['score']:.3f})") + print(f" Loaded in: {deberta_load_time:.2f}s") @@ - for i, pred in enumerate(deberta_result[0][:5]): # Top 5 - print(".3f") + for i, pred in enumerate(deberta_result[0][:5], start=1): # Top 5 + print(f" {i}. {pred['label']} ({pred['score']:.3f})")
158-161: Finalize summary with real values- print(".2f") - print(".3f") + print(f"Load time: {load_time:.2f}s") + print(f"Avg inference: {avg_inference_time:.3f}s")scripts/testing/deberta_simple_test.py (2)
64-67: Replace literal format placeholder with actual values- top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} + top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} print(f"Text: {text}") - print(".3f") + print(f" Top: {top_emotion['label']} ({top_emotion['score']:.3f})") print()
68-75: Use logger.exception for prediction and top-level failures- except Exception as e: - print(f"❌ Prediction failed for '{text}': {e}") + except Exception as e: + logger.exception(f"❌ Prediction failed for '{text}': {e}") @@ - except Exception as e: - print(f"❌ Simple test failed: {e}") + except Exception as e: + logger.exception(f"❌ Simple test failed: {e}")Also applies to: 73-79
scripts/testing/deberta_workaround.py (2)
24-28: Avoid fixed /tmp path; use a temp directoryPrevents collisions and S108 warning.
-from pathlib import Path +from pathlib import Path +import tempfile @@ - local_dir = Path("/tmp/deberta_manual") + local_dir = Path(tempfile.mkdtemp(prefix="deberta_manual_")) @@ - local_dir.mkdir(exist_ok=True) + local_dir.mkdir(exist_ok=True, parents=True)
142-147: Print predicted label and score instead of literal placeholderfor text in test_texts: result = predict_fn(text) print(f"Text: {text}") - print(".3f") + print(f" Top: {result['label']} ({result['score']:.3f})") print()scripts/deployment/deploy_deberta_model.py (1)
176-183: Fix generated instructions: endpoint pathAlign docs with /api/predict.
-# Test emotion detection -curl -X POST http://localhost:8080/detect-emotions \\ +# Test emotion detection +curl -X POST http://localhost:8080/api/predict \\ -H "Content-Type: application/json" \\ - -d '{{"text": "I am feeling happy today!"}}' + -d '{"text": "I am feeling happy today!"}'scripts/testing/debug_deberta_loading.py (1)
1-1: Consider making the file executable if intended for command-line use.The shebang is present, suggesting this script is meant to be executed directly from the command line. However, the file lacks executable permissions.
To make the file executable, run:
chmod +x scripts/testing/debug_deberta_loading.pyscripts/testing/cloud_run_deployment_monitor.py (2)
1-1: Consider making the file executable if intended for command-line use.The shebang is present, suggesting this script is meant to be executed directly from the command line. However, the file lacks executable permissions.
To make the file executable, run:
chmod +x scripts/testing/cloud_run_deployment_monitor.py
157-157: Consider using ASCII alternative for multiplication sign.The multiplication sign character (×) might not display correctly in all terminals or environments.
Consider replacing with the standard ASCII 'x':
- print(" 1. ✅ Comprehensive API testing (10 runs × 5 entries)") + print(" 1. ✅ Comprehensive API testing (10 runs x 5 entries)")scripts/testing/scientific_cloud_run_testing.py (2)
1-1: Consider making the file executable if intended for command-line use.The shebang is present, suggesting this script is meant to be executed directly from the command line. However, the file lacks executable permissions.
To make the file executable, run:
chmod +x scripts/testing/scientific_cloud_run_testing.py
472-472: Consider using ASCII alternative for multiplication sign.The multiplication sign character (×) might not display correctly in all terminals or environments.
Consider replacing with the standard ASCII 'x':
- print(" 1. Comprehensive API testing (10 runs × 5 entries)") + print(" 1. Comprehensive API testing (10 runs x 5 entries)")scripts/testing/deberta_journal_inference_demo.py (2)
1-1: Consider making the file executable if intended for command-line use.The shebang is present, suggesting this script is meant to be executed directly from the command line. However, the file lacks executable permissions.
To make the file executable, run:
chmod +x scripts/testing/deberta_journal_inference_demo.py
303-303: Add explicit Optional type hint for better clarity.PEP 484 prohibits implicit Optional. When a parameter can be None, it should be explicitly typed as Optional.
Apply this diff to add the explicit Optional type hint:
+from typing import Optional # ... other imports ... - def save_results(results: Dict[str, Any], filename: str = None) -> str: + def save_results(results: Dict[str, Any], filename: Optional[str] = None) -> str:scripts/testing/test_deberta_isolated.py (2)
1-1: Shebang present but file not executableThe shebang line suggests this should be an executable script, but the file permissions don't reflect that.
If this script is intended to be run directly, make it executable:
chmod +x scripts/testing/test_deberta_isolated.py
71-71: Remove redundant exception from logging.exceptionThe exception object is automatically included in
logger.exception.- logger.exception(f"❌ DeBERTa isolated test failed: {e}") + logger.exception("❌ DeBERTa isolated test failed")scripts/start_api_server.py (1)
111-111: Use logging.exception for error loggingWhen catching exceptions, use
logging.exceptioninstead oflogging.errorto include the traceback.- logger.error(f"❌ Failed to start server: {e}") + logger.exception("❌ Failed to start server")scripts/testing/quick_model_test.py (1)
48-48: Unused variable inference_timeThe inference_time variable is calculated but never used (except in the broken print statements).
Once the print statements are fixed, the static analysis warning will be resolved.
Also applies to: 101-101, 186-186
scripts/testing/comprehensive_journal_inference_demo.py (1)
220-224: Missing newline character in comment blockThe multi-line comment appears to be missing proper indentation structure.
Add proper spacing:
print(f"⚡ Processing Time: {prediction['processing_time_ms']:.2f}ms") + # Show top emotions with scores print("\n🏆 Top Emotions:") for emotion, score in zip(prediction['predicted_emotions'][:3], prediction['emotion_scores'][:3]): print(f" - {emotion}: {score:.3f}") + # Compare with expected emotionstests/test_unified_api_server.py (1)
1-1: Minor: Shebang in a test module.Not harmful, but tests aren’t executed as scripts. Consider removing the shebang to appease linters (EXE001).
src/models/unified_api_server.py (3)
238-308: Prefer logging.exception and preserve trace with exception chaining.Catching broad Exception is fine at API boundaries, but use logger.exception and “raise ... from e” for better diagnostics.
- except Exception as e: - logger.error(f"Transcription error: {e}") - raise HTTPException( - status_code=500, detail=f"Transcription failed: {str(e)}" - ) + except Exception as e: + logger.exception("Transcription error") + raise HTTPException(status_code=500, detail=f"Transcription failed: {e}") from eApply similarly in summarize/detect/process-audio handlers.
349-376: Optional: reuse MIME validation in the combined pipeline./process-audio writes the upload without verifying type. Reuse the same magic-based check from /transcribe to align behavior and error messages.
490-493: Optional: accept workers/reload for CLI parity.start_api_server.py likely passes workers/reload. Expose them to avoid TypeError.
- def run(self, host: str = "0.0.0.0", port: int = 8000): + def run(self, host: str = "0.0.0.0", port: int = 8000, workers: int = 1, reload: bool = False): """Run the API server.""" - logger.info(f"Starting SAMO Unified API Server on {host}:{port}") - uvicorn.run(self.app, host=host, port=port) + logger.info(f"Starting SAMO Unified API Server on {host}:{port} with {workers} worker(s)") + uvicorn.run(self.app, host=host, port=port, workers=workers, reload=reload)scripts/testing/model_comparison_test.py (2)
169-177: Remove unused variable and rename loop index.Tiny cleanups improve clarity.
- for i in range(num_runs): + for _ in range(num_runs): start_time = time.time() for text in test_texts: if model_key == 'bert_custom': - results = model.predict_emotions(text, threshold=0.5) + model.predict_emotions(text, threshold=0.5) else: - results = model(text) + model(text)
391-396: Prefer logger.exception for failures.Keeps stack traces without manual formatting.
- except Exception as e: - logger.error(f"❌ Benchmark failed: {e}") + except Exception: + logger.exception("❌ Benchmark failed")test_samo_emotion_detection_standalone.py (3)
9-17: Avoid sys.path hacks; import via package path or document run mode.This script inserts src into sys.path and imports models.*. Prefer importing via the package root (src.models...) or running in an environment with PYTHONPATH set. At minimum, keep imports consistent with the rest of the repo.
-# Add src to path -sys.path.insert(0, str(Path(__file__).parent / "src")) - -from models.emotion_detection.samo_bert_emotion_classifier import create_samo_bert_emotion_classifier -from models.emotion_detection.emotion_labels import get_all_emotions, get_emotion_description +from src.models.emotion_detection.samo_bert_emotion_classifier import create_samo_bert_emotion_classifier +from src.models.emotion_detection.emotion_labels import get_all_emotions, get_emotion_description
198-206: Reuse initialized model to speed up performance tests.You re-create the model multiple times; reuse where possible to avoid download/init cost.
1-1: Minor: shebang in a helper script.If you intend to run via
python file.py, drop the shebang or make the file executable; otherwise linters complain (EXE001).deployment/cloud-run/model_utils.py (3)
378-384: logger.exception already records the exception; avoid redundant%sand variable.Also applies to other logger.exception calls below.
- except Exception as e: - logger.exception("❌ Failed to load emotion model: %s", e) + except Exception: + logger.exception("❌ Failed to load emotion model")
485-541: Batch DeBERTa inference processes one-by-one; OK for simplicity—consider micro-batching.Not critical now; optional enhancement for throughput if needed.
121-176: CustomPipeline.call: guard label index and threshold as constants.You’re already guarding indices; consider pulling the 0.05 threshold to a module constant for clarity and future tuning.
src/models/emotion_detection/samo_bert_emotion_classifier.py (7)
1-1: Remove shebang or make file executable.Library modules shouldn’t include a shebang unless they’re intended to be executed. Drop it to satisfy EXE001.
-#!/usr/bin/env python3
29-35: Avoid module-level logging.basicConfig and global warnings filters.This changes global app behavior and hides useful warnings. Let the app configure logging and warnings.
-# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Suppress warnings for cleaner output -warnings.filterwarnings("ignore", category=UserWarning) +# Library logger (no global config) +logger = logging.getLogger(__name__)
201-207: Type hint: annotate threshold as Optional[float].Ruff RUF013: default None requires Optional[T].
- def predict_emotions( + def predict_emotions( self, texts: Union[str, List[str]], - threshold: float = None, + threshold: Optional[float] = None, top_k: Optional[int] = None, batch_size: int = 32, ) -> Dict[str, Union[List[str], List[float], List[List[int]]]]:
21-28: Add import for shared labels (supports previous change).from transformers import AutoConfig, AutoModel, AutoTokenizer +from .emotion_labels import GOEMOTIONS_EMOTIONS as EMOTION_LABELS # shared label source
293-297: Avoid .data; update temperature under no_grad.In-place .data usage is discouraged.
def set_temperature(self, temperature: float) -> None: """Set temperature scaling parameter.""" - self.temperature.data.fill_(temperature) + with torch.no_grad(): + self.temperature.fill_(temperature) logger.info(f"Set temperature to {temperature}")
211-219: Document top_k vs threshold precedence.When top_k is set, threshold is effectively ignored. Clarify in docstring to avoid surprises.
349-352: Style: long error messages inline.TRY003: factor to constants or shorten messages.
- raise ValueError( - f"Class weights shape {self.class_weights.shape} does not match " - f"number of classes {bce_loss.shape[1]}" - ) + raise ValueError(f"class_weights shape {tuple(self.class_weights.shape)} != num_classes {bce_loss.shape[1]}")- raise ValueError( - f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})." - ) + raise ValueError(f"labels length {len(labels)} != num_emotions {self.num_emotions}")Also applies to: 407-409
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
src/models/__pycache__/__init__.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/__init__.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/config.cpython-38.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/enhanced_bert_classifier.cpython-38.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/samo_bert_emotion_classifier.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/samo_bert_emotion_classifier.cpython-312.pycis excluded by!**/*.pyc
📒 Files selected for processing (25)
deployment/cloud-run/cloudbuild.yaml(1 hunks)deployment/cloud-run/model_utils.py(7 hunks)deployment/cloud-run/secure_api_server.py(4 hunks)push_log.txt(1 hunks)scripts/deployment/deploy_deberta_model.py(1 hunks)scripts/start_api_server.py(1 hunks)scripts/testing/cloud_run_deployment_monitor.py(1 hunks)scripts/testing/comprehensive_journal_inference_demo.py(1 hunks)scripts/testing/comprehensive_journal_inference_report.md(1 hunks)scripts/testing/deberta_journal_inference_demo.py(1 hunks)scripts/testing/deberta_safetensors_test.py(1 hunks)scripts/testing/deberta_simple_test.py(1 hunks)scripts/testing/deberta_workaround.py(1 hunks)scripts/testing/debug_deberta_loading.py(1 hunks)scripts/testing/model_comparison_test.py(1 hunks)scripts/testing/quick_deployment_check.py(1 hunks)scripts/testing/quick_model_test.py(1 hunks)scripts/testing/scientific_cloud_run_testing.py(1 hunks)scripts/testing/test_deberta_api.py(1 hunks)scripts/testing/test_deberta_isolated.py(1 hunks)scripts/testing/test_model_switching.py(1 hunks)src/models/emotion_detection/samo_bert_emotion_classifier.py(1 hunks)src/models/unified_api_server.py(1 hunks)test_samo_emotion_detection_standalone.py(1 hunks)tests/test_unified_api_server.py(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- scripts/testing/comprehensive_journal_inference_report.md
- push_log.txt
🧰 Additional context used
🧬 Code graph analysis (20)
scripts/testing/deberta_workaround.py (3)
scripts/deployment/deploy_deberta_model.py (1)
main(231-265)scripts/testing/deberta_safetensors_test.py (1)
main(138-163)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
scripts/testing/deberta_safetensors_test.py (1)
scripts/testing/deberta_simple_test.py (2)
compare_with_production(80-98)main(100-114)
scripts/testing/test_deberta_api.py (1)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)
scripts/testing/test_model_switching.py (1)
deployment/cloud-run/model_utils.py (2)
ensure_model_loaded(223-384)predict_emotions(387-464)
tests/test_unified_api_server.py (2)
src/models/unified_api_server.py (1)
SAMOUnifiedAPIServer(134-493)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
scripts/testing/debug_deberta_loading.py (2)
scripts/deployment/deploy_deberta_model.py (1)
main(231-265)scripts/testing/model_comparison_test.py (1)
main(369-396)
scripts/testing/test_deberta_isolated.py (2)
scripts/deployment/deploy_deberta_model.py (1)
test_deberta_loading(68-105)deployment/cloud-run/model_utils.py (3)
ensure_model_loaded(223-384)predict_emotions(387-464)get_model_status(467-482)
scripts/deployment/deploy_deberta_model.py (2)
scripts/testing/test_deberta_isolated.py (1)
test_deberta_loading(21-72)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
scripts/testing/comprehensive_journal_inference_demo.py (2)
scripts/testing/deberta_journal_inference_demo.py (6)
load_model(47-74)predict_emotions(76-143)create_journal_entries(146-174)run_comprehensive_demo(176-300)save_results(303-315)main(318-344)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
scripts/testing/quick_model_test.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)
scripts/testing/scientific_cloud_run_testing.py (2)
deployment/cloud-run/secure_api_server.py (2)
post(299-335)post(348-393)scripts/testing/cloud_run_deployment_monitor.py (1)
main(186-224)
scripts/testing/model_comparison_test.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)deployment/cloud-run/model_utils.py (1)
predict_emotions(387-464)
scripts/testing/cloud_run_deployment_monitor.py (3)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)scripts/testing/test_deberta_api.py (1)
main(154-196)scripts/testing/quick_deployment_check.py (1)
main(89-111)
src/models/unified_api_server.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)
scripts/testing/deberta_simple_test.py (2)
scripts/testing/deberta_safetensors_test.py (2)
compare_with_production(111-136)main(138-163)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
src/models/emotion_detection/samo_bert_emotion_classifier.py (4)
deployment/cloud-run/secure_api_server.py (4)
get(265-286)get(400-411)get(421-430)get(439-453)deployment/cloud-run/model_utils.py (1)
predict_emotions(387-464)scripts/testing/comprehensive_journal_inference_demo.py (1)
predict_emotions(81-141)scripts/testing/deberta_journal_inference_demo.py (1)
predict_emotions(76-143)
scripts/testing/deberta_journal_inference_demo.py (1)
scripts/testing/comprehensive_journal_inference_demo.py (6)
load_model(58-79)predict_emotions(81-141)create_journal_entries(144-172)run_comprehensive_demo(174-271)save_results(274-286)main(289-314)
scripts/testing/quick_deployment_check.py (3)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)scripts/testing/cloud_run_deployment_monitor.py (1)
main(186-224)scripts/testing/test_deberta_api.py (1)
main(154-196)
scripts/start_api_server.py (1)
src/models/unified_api_server.py (2)
SAMOUnifiedAPIServer(134-493)run(490-493)
test_samo_emotion_detection_standalone.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (5)
create_samo_bert_emotion_classifier(424-464)count_parameters(298-300)count_frozen_parameters(302-304)predict_emotions(201-291)set_temperature(293-296)src/models/emotion_detection/emotion_labels.py (2)
get_all_emotions(269-276)get_emotion_description(243-253)
🪛 Ruff (0.12.2)
scripts/testing/deberta_workaround.py
1-1: Shebang is present but file is not executable
(EXE001)
24-24: Probable insecure usage of temporary file or directory: "/tmp/deberta_manual"
(S108)
51-51: Do not catch blind exception: Exception
(BLE001)
89-89: Consider moving this statement to an else block
(TRY300)
91-91: Do not catch blind exception: Exception
(BLE001)
scripts/testing/deberta_safetensors_test.py
1-1: Shebang is present but file is not executable
(EXE001)
61-61: Consider moving this statement to an else block
(TRY300)
63-63: Do not catch blind exception: Exception
(BLE001)
103-103: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
104-104: Local variable top_emotion is assigned to but never used
Remove assignment to unused variable top_emotion
(F841)
135-135: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
135-135: Loop control variable pred not used within loop body
Rename unused pred to _pred
(B007)
scripts/testing/test_deberta_api.py
1-1: Shebang is present but file is not executable
(EXE001)
36-36: Consider moving this statement to an else block
(TRY300)
38-38: Do not catch blind exception: Exception
(BLE001)
39-39: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
64-64: Unnecessary key check before dictionary access
Replace with dict.get
(RUF019)
66-66: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
66-66: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
77-77: Consider moving this statement to an else block
(TRY300)
79-79: Do not catch blind exception: Exception
(BLE001)
80-80: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
106-106: Consider moving this statement to an else block
(TRY300)
108-108: Do not catch blind exception: Exception
(BLE001)
109-109: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
141-141: Unnecessary key check before dictionary access
Replace with dict.get
(RUF019)
148-148: Consider moving this statement to an else block
(TRY300)
150-150: Do not catch blind exception: Exception
(BLE001)
151-151: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
scripts/testing/test_model_switching.py
1-1: Shebang is present but file is not executable
(EXE001)
49-49: Do not catch blind exception: Exception
(BLE001)
84-84: Do not catch blind exception: Exception
(BLE001)
tests/test_unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
scripts/testing/debug_deberta_loading.py
1-1: Shebang is present but file is not executable
(EXE001)
52-52: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
60-60: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
65-65: Consider moving this statement to an else block
(TRY300)
67-67: Do not catch blind exception: Exception
(BLE001)
89-89: Do not catch blind exception: Exception
(BLE001)
94-94: Do not catch blind exception: Exception
(BLE001)
106-106: Do not catch blind exception: Exception
(BLE001)
115-115: Do not catch blind exception: Exception
(BLE001)
119-119: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
137-137: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
142-142: Consider moving this statement to an else block
(TRY300)
144-144: Do not catch blind exception: Exception
(BLE001)
159-159: Probable insecure usage of temporary file or directory: "/tmp/deberta_cache"
(S108)
164-164: Local variable local_dir is assigned to but never used
Remove assignment to unused variable local_dir
(F841)
170-170: Local variable download_time is assigned to but never used
Remove assignment to unused variable download_time
(F841)
187-187: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
195-195: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
200-200: Consider moving this statement to an else block
(TRY300)
202-202: Do not catch blind exception: Exception
(BLE001)
230-230: Do not catch blind exception: Exception
(BLE001)
scripts/testing/test_deberta_isolated.py
1-1: Shebang is present but file is not executable
(EXE001)
65-65: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
65-65: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
68-68: Consider moving this statement to an else block
(TRY300)
71-71: Redundant exception object included in logging.exception call
(TRY401)
scripts/deployment/deploy_deberta_model.py
1-1: Shebang is present but file is not executable
(EXE001)
101-101: Consider moving this statement to an else block
(TRY300)
103-103: Do not catch blind exception: Exception
(BLE001)
131-131: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
scripts/testing/comprehensive_journal_inference_demo.py
1-1: Shebang is present but file is not executable
(EXE001)
24-24: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
40-40: Avoid specifying long messages outside the exception class
(TRY003)
75-75: Consider moving this statement to an else block
(TRY300)
77-77: Do not catch blind exception: Exception
(BLE001)
131-131: Do not catch blind exception: Exception
(BLE001)
274-274: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
311-311: Do not catch blind exception: Exception
(BLE001)
scripts/testing/quick_model_test.py
1-1: Shebang is present but file is not executable
(EXE001)
48-48: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
62-62: Consider moving this statement to an else block
(TRY300)
64-64: Do not catch blind exception: Exception
(BLE001)
101-101: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
110-110: Local variable emotion is assigned to but never used
Remove assignment to unused variable emotion
(F841)
111-111: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
116-116: Consider moving this statement to an else block
(TRY300)
118-118: Do not catch blind exception: Exception
(BLE001)
147-147: Do not catch blind exception: Exception
(BLE001)
164-164: Do not catch blind exception: Exception
(BLE001)
186-186: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
195-195: Local variable emotion is assigned to but never used
Remove assignment to unused variable emotion
(F841)
196-196: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
201-201: Consider moving this statement to an else block
(TRY300)
203-203: Do not catch blind exception: Exception
(BLE001)
scripts/testing/scientific_cloud_run_testing.py
1-1: Shebang is present but file is not executable
(EXE001)
199-199: SyntaxError: Expected ',', found name
202-202: SyntaxError: Expected ',', found name
205-205: SyntaxError: Expected ',', found name
205-206: SyntaxError: Expected ')', found newline
222-222: SyntaxError: missing closing quote in string literal
223-223: SyntaxError: Got unexpected token 🔥
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found string
223-223: SyntaxError: Got unexpected token ⚡
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found ':'
223-223: SyntaxError: missing closing quote in string literal
226-226: SyntaxError: Expected ',', found name
228-228: SyntaxError: Expected ',', found name
228-229: SyntaxError: Expected ')', found newline
243-243: SyntaxError: Expected ',', found name
244-244: SyntaxError: Expected ',', found name
246-246: SyntaxError: Expected ',', found name
247-247: SyntaxError: Expected ',', found name
249-249: SyntaxError: Expected ',', found name
249-250: SyntaxError: Expected ')', found newline
265-265: SyntaxError: missing closing quote in string literal
266-266: SyntaxError: Got unexpected token 🔄
266-266: SyntaxError: Expected ',', found name
266-266: SyntaxError: Expected ',', found string
266-266: SyntaxError: Got unexpected token 📊
266-266: SyntaxError: Expected ',', found ':'
266-266: SyntaxError: missing closing quote in string literal
269-269: SyntaxError: Expected ',', found name
270-270: SyntaxError: Expected ',', found name
272-272: SyntaxError: Expected ',', found name
273-273: SyntaxError: Expected ',', found name
273-274: SyntaxError: Expected ')', found newline
361-361: SyntaxError: Expected ',', found name
362-362: SyntaxError: Expected ',', found name
363-363: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
365-365: SyntaxError: Expected ',', found name
366-366: SyntaxError: Expected ',', found name
366-366: SyntaxError: Expected ',', found name
367-367: SyntaxError: Expected ',', found name
368-368: SyntaxError: Expected ',', found name
369-369: SyntaxError: Expected ',', found name
369-369: SyntaxError: Expected ',', found name
370-370: SyntaxError: Expected ',', found name
372-372: SyntaxError: Expected ',', found name
372-373: SyntaxError: Expected ')', found NonLogicalNewline
scripts/testing/model_comparison_test.py
1-1: Shebang is present but file is not executable
(EXE001)
66-66: Consider moving this statement to an else block
(TRY300)
67-67: Do not catch blind exception: Exception
(BLE001)
87-87: Do not catch blind exception: Exception
(BLE001)
110-110: Consider moving this statement to an else block
(TRY300)
111-111: Do not catch blind exception: Exception
(BLE001)
133-133: Consider moving this statement to an else block
(TRY300)
134-134: Do not catch blind exception: Exception
(BLE001)
169-169: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
176-176: Local variable results is assigned to but never used
Remove assignment to unused variable results
(F841)
226-226: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
249-249: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
249-249: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
391-391: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
scripts/testing/cloud_run_deployment_monitor.py
1-1: Shebang is present but file is not executable
(EXE001)
33-33: subprocess call: check for execution of untrusted input
(S603)
40-40: Consider moving this statement to an else block
(TRY300)
45-45: Do not catch blind exception: Exception
(BLE001)
60-60: Consider moving this statement to an else block
(TRY300)
68-68: Do not catch blind exception: Exception
(BLE001)
94-94: Do not catch blind exception: Exception
(BLE001)
157-157: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF001)
169-169: Undefined name os
(F821)
173-173: subprocess call: check for execution of untrusted input
(S603)
182-182: Do not catch blind exception: Exception
(BLE001)
src/models/unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
176-176: Do not catch blind exception: Exception
(BLE001)
177-177: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
185-185: Do not catch blind exception: Exception
(BLE001)
186-186: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
195-195: Do not catch blind exception: Exception
(BLE001)
196-196: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
232-232: Do not catch blind exception: Exception
(BLE001)
233-233: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
234-236: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
235-235: Use explicit conversion flag
Replace with conversion flag
(RUF010)
241-241: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
304-304: Do not catch blind exception: Exception
(BLE001)
305-305: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
306-308: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
307-307: Use explicit conversion flag
Replace with conversion flag
(RUF010)
343-343: Do not catch blind exception: Exception
(BLE001)
344-344: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
345-347: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
346-346: Use explicit conversion flag
Replace with conversion flag
(RUF010)
352-352: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
365-367: Abstract raise to an inner function
(TRY301)
370-370: Undefined name uuid
(F821)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
461-461: Do not catch blind exception: Exception
(BLE001)
462-462: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
463-465: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
464-464: Use explicit conversion flag
Replace with conversion flag
(RUF010)
490-490: Possible binding to all interfaces
(S104)
scripts/testing/deberta_simple_test.py
1-1: Shebang is present but file is not executable
(EXE001)
64-64: Local variable top_emotion is assigned to but never used
Remove assignment to unused variable top_emotion
(F841)
68-68: Do not catch blind exception: Exception
(BLE001)
71-71: Consider moving this statement to an else block
(TRY300)
73-73: Do not catch blind exception: Exception
(BLE001)
src/models/emotion_detection/samo_bert_emotion_classifier.py
1-1: Shebang is present but file is not executable
(EXE001)
204-204: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
349-352: Avoid specifying long messages outside the exception class
(TRY003)
407-409: Avoid specifying long messages outside the exception class
(TRY003)
deployment/cloud-run/model_utils.py
75-75: Unused method argument: kwargs
(ARG002)
132-132: Avoid specifying long messages outside the exception class
(TRY003)
175-175: Consider moving this statement to an else block
(TRY300)
178-178: Redundant exception object included in logging.exception call
(TRY401)
288-288: Do not catch blind exception: Exception
(BLE001)
293-293: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
315-315: Local variable max_length is assigned to but never used
Remove assignment to unused variable max_length
(F841)
327-327: Do not catch blind exception: Exception
(BLE001)
332-332: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
366-366: Do not catch blind exception: Exception
(BLE001)
534-534: Redundant exception object included in logging.exception call
(TRY401)
scripts/testing/deberta_journal_inference_demo.py
1-1: Shebang is present but file is not executable
(EXE001)
70-70: Consider moving this statement to an else block
(TRY300)
72-72: Do not catch blind exception: Exception
(BLE001)
133-133: Do not catch blind exception: Exception
(BLE001)
303-303: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
341-341: Do not catch blind exception: Exception
(BLE001)
scripts/testing/quick_deployment_check.py
1-1: Shebang is present but file is not executable
(EXE001)
65-65: SyntaxError: Expected ',', found name
66-66: SyntaxError: Expected ',', found name
67-67: SyntaxError: Expected ',', found name
68-68: SyntaxError: Expected ',', found name
68-69: SyntaxError: Expected ')', found newline
scripts/start_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
26-26: Possible binding to all interfaces
(S104)
67-67: Undefined name logger
(F821)
68-68: Do not catch blind exception: Exception
(BLE001)
69-69: Undefined name logger
(F821)
70-70: Undefined name logger
(F821)
110-110: Do not catch blind exception: Exception
(BLE001)
111-111: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
test_samo_emotion_detection_standalone.py
1-1: Shebang is present but file is not executable
(EXE001)
100-100: Use explicit conversion flag
Replace with conversion flag
(RUF010)
105-105: Do not catch blind exception: Exception
(BLE001)
224-224: Do not catch blind exception: Exception
(BLE001)
276-276: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (3)
scripts/deployment/deploy_deberta_model.py (1)
14-17: Update usage curl to match API surfaceThe unified server uses POST /api/predict with {"text": "..."}.
- curl -X POST http://localhost:8000/detect-emotions \\ + curl -X POST http://localhost:8000/api/predict \\ -H "Content-Type: application/json" \\ - -d '{"text": "I am feeling happy today!"}' + -d '{"text": "I am feeling happy today!"}'tests/test_unified_api_server.py (1)
329-357: Nice fix on model unavailability test.Using api_server.models with deepcopy and restoring in finally block is correct and robust.
deployment/cloud-run/model_utils.py (1)
409-434: Consistent formatting of returned emotions; good shape alignment.Nice normalization between DeBERTa direct path and pipeline path.
| model = AutoModelForSequenceClassification.from_pretrained( | ||
| EMOTION_MODEL_DIR, local_files_only=True | ||
| EMOTION_MODEL_DIR, local_files_only=True, torch_dtype="float32" | ||
| ) | ||
| emotion_pipeline = _create_emotion_pipeline(tokenizer, model) | ||
| logger.info("✅ Emotion model loaded from local directory") |
There was a problem hiding this comment.
torch_dtype should be a torch.dtype, not a string.
Passing "float32" as a string can be ignored or misinterpreted. Use torch.float32 consistently.
- model = AutoModelForSequenceClassification.from_pretrained(
- EMOTION_MODEL_DIR, local_files_only=True, torch_dtype="float32"
- )
+ model = AutoModelForSequenceClassification.from_pretrained(
+ EMOTION_MODEL_DIR, local_files_only=True, torch_dtype=torch.float32
+ )📝 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.
| model = AutoModelForSequenceClassification.from_pretrained( | |
| EMOTION_MODEL_DIR, local_files_only=True | |
| EMOTION_MODEL_DIR, local_files_only=True, torch_dtype="float32" | |
| ) | |
| emotion_pipeline = _create_emotion_pipeline(tokenizer, model) | |
| logger.info("✅ Emotion model loaded from local directory") | |
| model = AutoModelForSequenceClassification.from_pretrained( | |
| EMOTION_MODEL_DIR, local_files_only=True, torch_dtype=torch.float32 | |
| ) | |
| emotion_pipeline = _create_emotion_pipeline(tokenizer, model) | |
| logger.info("✅ Emotion model loaded from local directory") |
🤖 Prompt for AI Agents
In deployment/cloud-run/model_utils.py around lines 256 to 260, the call to
AutoModelForSequenceClassification.from_pretrained passes torch_dtype="float32"
as a string; change it to torch.float32 (a torch.dtype) and ensure torch is
imported in this module (add/import torch at top if missing) so the dtype is
passed correctly to the model loader.
| model_name = DEBERTA_MODEL_NAME | ||
| model_kwargs = { | ||
| "torch_dtype": "float32", | ||
| "use_safetensors": True, | ||
| "ignore_mismatched_sizes": True | ||
| } | ||
| max_length = 256 | ||
| model_type = "DeBERTa (28 emotions)" | ||
|
|
||
| # DIRECT LOADING FOR DeBERTa - bypass pipeline entirely | ||
| logger.info(f"🔧 Loading {model_type} with direct approach (bypassing pipeline)") | ||
|
|
||
| try: | ||
| # Load tokenizer and model directly with slow tokenizer | ||
| emotion_tokenizer = AutoTokenizer.from_pretrained( | ||
| model_name, use_fast=False | ||
| ) | ||
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | ||
| model_name, **model_kwargs | ||
| ) | ||
| # Set pipeline to None for DeBERTa (we use direct inference) | ||
| emotion_pipeline = None | ||
| logger.info(f"✅ {model_type} loaded successfully with direct approach") | ||
|
|
||
| except Exception as direct_load_error: | ||
| logger.warning(f"Direct loading failed, trying download approach: {direct_load_error}") | ||
|
|
||
| # Force download the model | ||
| from huggingface_hub import snapshot_download | ||
| download_dir = f"/tmp/{model_name.replace('/', '_')}" | ||
| model_path = snapshot_download( | ||
| repo_id=model_name, | ||
| local_dir=download_dir, | ||
| local_dir_use_symlinks=False | ||
| ) | ||
| logger.info("📥 Model downloaded to: %s", model_path) | ||
|
|
||
| # Load from downloaded directory with direct approach | ||
| emotion_tokenizer = AutoTokenizer.from_pretrained( | ||
| download_dir, local_files_only=True, use_fast=False | ||
| ) | ||
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | ||
| download_dir, local_files_only=True, **model_kwargs | ||
| ) | ||
| emotion_pipeline = None | ||
| logger.info(f"✅ {model_type} loaded from downloaded files (direct approach)") | ||
|
|
||
| else: | ||
| # PRODUCTION MODEL - use pipeline approach (works fine) | ||
| model_name = PRODUCTION_MODEL_NAME | ||
| model_kwargs = {"torch_dtype": "float32"} | ||
| max_length = 512 | ||
| model_type = "Production (6 emotions)" | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
DeBERTa/production branches: use torch.float32 and drop unused max_length.
Clean up dtype and remove dead vars.
- model_kwargs = {
- "torch_dtype": "float32",
+ model_kwargs = {
+ "torch_dtype": torch.float32,
"use_safetensors": True,
"ignore_mismatched_sizes": True
}
- max_length = 256
model_type = "DeBERTa (28 emotions)"
@@
- model_kwargs = {"torch_dtype": "float32"}
- max_length = 512
+ model_kwargs = {"torch_dtype": torch.float32}
model_type = "Production (6 emotions)"📝 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.
| model_name = DEBERTA_MODEL_NAME | |
| model_kwargs = { | |
| "torch_dtype": "float32", | |
| "use_safetensors": True, | |
| "ignore_mismatched_sizes": True | |
| } | |
| max_length = 256 | |
| model_type = "DeBERTa (28 emotions)" | |
| # DIRECT LOADING FOR DeBERTa - bypass pipeline entirely | |
| logger.info(f"🔧 Loading {model_type} with direct approach (bypassing pipeline)") | |
| try: | |
| # Load tokenizer and model directly with slow tokenizer | |
| emotion_tokenizer = AutoTokenizer.from_pretrained( | |
| model_name, use_fast=False | |
| ) | |
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | |
| model_name, **model_kwargs | |
| ) | |
| # Set pipeline to None for DeBERTa (we use direct inference) | |
| emotion_pipeline = None | |
| logger.info(f"✅ {model_type} loaded successfully with direct approach") | |
| except Exception as direct_load_error: | |
| logger.warning(f"Direct loading failed, trying download approach: {direct_load_error}") | |
| # Force download the model | |
| from huggingface_hub import snapshot_download | |
| download_dir = f"/tmp/{model_name.replace('/', '_')}" | |
| model_path = snapshot_download( | |
| repo_id=model_name, | |
| local_dir=download_dir, | |
| local_dir_use_symlinks=False | |
| ) | |
| logger.info("📥 Model downloaded to: %s", model_path) | |
| # Load from downloaded directory with direct approach | |
| emotion_tokenizer = AutoTokenizer.from_pretrained( | |
| download_dir, local_files_only=True, use_fast=False | |
| ) | |
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | |
| download_dir, local_files_only=True, **model_kwargs | |
| ) | |
| emotion_pipeline = None | |
| logger.info(f"✅ {model_type} loaded from downloaded files (direct approach)") | |
| else: | |
| # PRODUCTION MODEL - use pipeline approach (works fine) | |
| model_name = PRODUCTION_MODEL_NAME | |
| model_kwargs = {"torch_dtype": "float32"} | |
| max_length = 512 | |
| model_type = "Production (6 emotions)" | |
| model_name = DEBERTA_MODEL_NAME | |
| model_kwargs = { | |
| "torch_dtype": torch.float32, | |
| "use_safetensors": True, | |
| "ignore_mismatched_sizes": True | |
| } | |
| model_type = "DeBERTa (28 emotions)" | |
| # DIRECT LOADING FOR DeBERTa - bypass pipeline entirely | |
| logger.info(f"🔧 Loading {model_type} with direct approach (bypassing pipeline)") | |
| try: | |
| # Load tokenizer and model directly with slow tokenizer | |
| emotion_tokenizer = AutoTokenizer.from_pretrained( | |
| model_name, use_fast=False | |
| ) | |
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | |
| model_name, **model_kwargs | |
| ) | |
| # Set pipeline to None for DeBERTa (we use direct inference) | |
| emotion_pipeline = None | |
| logger.info(f"✅ {model_type} loaded successfully with direct approach") | |
| except Exception as direct_load_error: | |
| logger.warning(f"Direct loading failed, trying download approach: {direct_load_error}") | |
| # Force download the model | |
| from huggingface_hub import snapshot_download | |
| download_dir = f"/tmp/{model_name.replace('/', '_')}" | |
| model_path = snapshot_download( | |
| repo_id=model_name, | |
| local_dir=download_dir, | |
| local_dir_use_symlinks=False | |
| ) | |
| logger.info("📥 Model downloaded to: %s", model_path) | |
| # Load from downloaded directory with direct approach | |
| emotion_tokenizer = AutoTokenizer.from_pretrained( | |
| download_dir, local_files_only=True, use_fast=False | |
| ) | |
| emotion_model = AutoModelForSequenceClassification.from_pretrained( | |
| download_dir, local_files_only=True, **model_kwargs | |
| ) | |
| emotion_pipeline = None | |
| logger.info(f"✅ {model_type} loaded from downloaded files (direct approach)") | |
| else: | |
| # PRODUCTION MODEL - use pipeline approach (works fine) | |
| model_name = PRODUCTION_MODEL_NAME | |
| model_kwargs = {"torch_dtype": torch.float32} | |
| model_type = "Production (6 emotions)" |
🧰 Tools
🪛 Ruff (0.12.2)
288-288: Do not catch blind exception: Exception
(BLE001)
293-293: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
315-315: Local variable max_length is assigned to but never used
Remove assignment to unused variable max_length
(F841)
🤖 Prompt for AI Agents
In deployment/cloud-run/model_utils.py around lines 264 to 317, change
model_kwargs to use the actual torch.dtype object (torch.float32) instead of the
string "float32" in both the DeBERTa and production branches and remove the
unused max_length variables (delete the assignments and any local references in
this block); add an import for torch at the top of the file if not present;
ensure all subsequent model loading calls still receive model_kwargs with the
dtype object and that removing max_length does not break any later code (if
needed, move max_length to the caller that actually needs it).
| # Apply configuration if available | ||
| if config: | ||
| # Apply server configuration | ||
| if 'server' in config: | ||
| server_config = config['server'] | ||
| if 'cors_origins' in server_config: | ||
| os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) | ||
| logger.info("✅ Applied server configuration") | ||
|
|
There was a problem hiding this comment.
CORS configuration is applied after server initialization
Setting the environment variable after server instantiation means the CORS configuration won't take effect. The SAMOUnifiedAPIServer reads CORS settings during initialization.
Move the environment variable setting before server creation:
+ # Apply configuration before creating server
+ if config and 'server' in config:
+ server_config = config['server']
+ if 'cors_origins' in server_config:
+ os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins'])
+ logger.info("✅ Applied server configuration")
+
# Create and start server with configuration
server = SAMOUnifiedAPIServer()
-
- # Apply configuration if available
- if config:
- # Apply server configuration
- if 'server' in config:
- server_config = config['server']
- if 'cors_origins' in server_config:
- os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins'])
- logger.info("✅ Applied server configuration")📝 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.
| # Apply configuration if available | |
| if config: | |
| # Apply server configuration | |
| if 'server' in config: | |
| server_config = config['server'] | |
| if 'cors_origins' in server_config: | |
| os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) | |
| logger.info("✅ Applied server configuration") | |
| # Apply configuration before creating server | |
| if config and 'server' in config: | |
| server_config = config['server'] | |
| if 'cors_origins' in server_config: | |
| os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) | |
| logger.info("✅ Applied server configuration") | |
| # Create and start server with configuration | |
| server = SAMOUnifiedAPIServer() |
🤖 Prompt for AI Agents
In scripts/start_api_server.py around lines 91-99, the code sets
API_ALLOWED_ORIGINS after the server is created so CORS won't be picked up; move
the block that reads config['server']['cors_origins'] and sets
os.environ['API_ALLOWED_ORIGINS'] to execute before instantiating
SAMOUnifiedAPIServer (i.e., apply the env var as soon as config is loaded and
before any server creation code), and remove or avoid duplicating the env
setting later so the server reads the correct CORS origins during
initialization.
| # Apply sigmoid to get probabilities | ||
| probabilities = torch.sigmoid(logits) | ||
|
|
||
| # Compute BCE loss | ||
| bce_loss = F.binary_cross_entropy( | ||
| probabilities, targets.float(), reduction="none" | ||
| ) | ||
|
|
||
| # Apply class weights if provided | ||
| if self.class_weights is not None: | ||
| # Ensure class weights are properly shaped for broadcasting | ||
| if self.class_weights.shape[0] != bce_loss.shape[1]: | ||
| raise ValueError( | ||
| f"Class weights shape {self.class_weights.shape} does not match " | ||
| f"number of classes {bce_loss.shape[1]}" | ||
| ) | ||
| bce_loss = bce_loss * self.class_weights.unsqueeze(0) | ||
|
|
||
| # Apply reduction | ||
| if self.reduction == "mean": | ||
| return bce_loss.mean() | ||
| if self.reduction == "sum": | ||
| return bce_loss.sum() | ||
| return bce_loss |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use BCEWithLogitsLoss for numerical stability (avoid sigmoid + BCE).
Current loss is less stable and double-applies sigmoid. Switch to binary_cross_entropy_with_logits with broadcastable class weights.
- # Apply sigmoid to get probabilities
- probabilities = torch.sigmoid(logits)
-
- # Compute BCE loss
- bce_loss = F.binary_cross_entropy(
- probabilities, targets.float(), reduction="none"
- )
+ # Compute stable BCE-with-logits directly on logits
+ targets_f = targets.float()
+ weight = None
+ if self.class_weights is not None:
+ # [C] -> [1, C] broadcast across batch
+ weight = self.class_weights.unsqueeze(0)
+ bce_loss = F.binary_cross_entropy_with_logits(
+ logits, targets_f, weight=weight, reduction="none"
+ )Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.12.2)
349-352: Avoid specifying long messages outside the exception class
(TRY003)
🤖 Prompt for AI Agents
In src/models/emotion_detection/samo_bert_emotion_classifier.py around lines
337-360, replace the current torch.sigmoid(...) + F.binary_cross_entropy(...)
pattern with torch.nn.functional.binary_cross_entropy_with_logits(...) using the
raw logits for numerical stability; validate that self.class_weights (if
provided) is a 1-D tensor of length num_classes and pass it as pos_weight (not
unsqueezed) to binary_cross_entropy_with_logits, preserve the existing reduction
behavior ("none"/"mean"/"sum") and ensure targets are cast to float and on the
same device/dtype as logits before calling the loss function.
| def __init__( | ||
| self, | ||
| texts: List[str], | ||
| labels: List[List[int]], | ||
| tokenizer: AutoTokenizer, | ||
| max_length: int = 512, | ||
| ) -> None: | ||
| """ | ||
| Initialize emotion dataset. | ||
|
|
||
| Args: | ||
| texts: List of text samples | ||
| labels: List of label lists (multi-label) | ||
| tokenizer: BERT tokenizer | ||
| max_length: Maximum sequence length | ||
| """ | ||
| self.texts = texts | ||
| self.labels = labels | ||
| self.tokenizer = tokenizer | ||
| self.max_length = max_length | ||
|
|
||
| def __len__(self) -> int: | ||
| """Return dataset length.""" | ||
| return len(self.texts) | ||
|
|
||
| def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]: | ||
| """Get item at index.""" | ||
| text = self.texts[idx] | ||
| labels = self.labels[idx] | ||
|
|
||
| # Tokenize text | ||
| encoding = self.tokenizer( | ||
| text, | ||
| truncation=True, | ||
| padding="max_length", | ||
| max_length=self.max_length, | ||
| return_tensors="pt", | ||
| ) | ||
|
|
||
| # Validate label length | ||
| if len(labels) != self.num_emotions: | ||
| raise ValueError( | ||
| f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})." | ||
| ) | ||
|
|
||
| # Convert labels to tensor |
There was a problem hiding this comment.
Bug: EmotionDataset references self.num_emotions which is never set.
This will raise at runtime. Pass and store num_emotions; also prefer tokenizer base type for typing.
- def __init__(
- self,
- texts: List[str],
- labels: List[List[int]],
- tokenizer: AutoTokenizer,
- max_length: int = 512,
- ) -> None:
+ def __init__(
+ self,
+ texts: List[str],
+ labels: List[List[int]],
+ tokenizer: AutoTokenizer,
+ max_length: int = 512,
+ num_emotions: int = 28,
+ ) -> None:
@@
- self.max_length = max_length
+ self.max_length = max_length
+ self.num_emotions = num_emotions
@@
- if len(labels) != self.num_emotions:
+ if len(labels) != self.num_emotions:
raise ValueError(
f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})."
)Follow-up: ensure all EmotionDataset(...) call sites pass num_emotions or rely on default 28.
📝 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.
| def __init__( | |
| self, | |
| texts: List[str], | |
| labels: List[List[int]], | |
| tokenizer: AutoTokenizer, | |
| max_length: int = 512, | |
| ) -> None: | |
| """ | |
| Initialize emotion dataset. | |
| Args: | |
| texts: List of text samples | |
| labels: List of label lists (multi-label) | |
| tokenizer: BERT tokenizer | |
| max_length: Maximum sequence length | |
| """ | |
| self.texts = texts | |
| self.labels = labels | |
| self.tokenizer = tokenizer | |
| self.max_length = max_length | |
| def __len__(self) -> int: | |
| """Return dataset length.""" | |
| return len(self.texts) | |
| def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]: | |
| """Get item at index.""" | |
| text = self.texts[idx] | |
| labels = self.labels[idx] | |
| # Tokenize text | |
| encoding = self.tokenizer( | |
| text, | |
| truncation=True, | |
| padding="max_length", | |
| max_length=self.max_length, | |
| return_tensors="pt", | |
| ) | |
| # Validate label length | |
| if len(labels) != self.num_emotions: | |
| raise ValueError( | |
| f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})." | |
| ) | |
| # Convert labels to tensor | |
| def __init__( | |
| self, | |
| texts: List[str], | |
| labels: List[List[int]], | |
| tokenizer: AutoTokenizer, | |
| max_length: int = 512, | |
| num_emotions: int = 28, | |
| ) -> None: | |
| """ | |
| Initialize emotion dataset. | |
| Args: | |
| texts: List of text samples | |
| labels: List of label lists (multi-label) | |
| tokenizer: BERT tokenizer | |
| max_length: Maximum sequence length | |
| """ | |
| self.texts = texts | |
| self.labels = labels | |
| self.tokenizer = tokenizer | |
| self.max_length = max_length | |
| self.num_emotions = num_emotions | |
| def __len__(self) -> int: | |
| """Return dataset length.""" | |
| return len(self.texts) | |
| def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]: | |
| """Get item at index.""" | |
| text = self.texts[idx] | |
| labels = self.labels[idx] | |
| # Tokenize text | |
| encoding = self.tokenizer( | |
| text, | |
| truncation=True, | |
| padding="max_length", | |
| max_length=self.max_length, | |
| return_tensors="pt", | |
| ) | |
| # Validate label length | |
| if len(labels) != self.num_emotions: | |
| raise ValueError( | |
| f"Label list length ({len(labels)}) does not match expected number of emotions ({self.num_emotions})." | |
| ) | |
| # Convert labels to tensor |
🧰 Tools
🪛 Ruff (0.12.2)
407-409: Avoid specifying long messages outside the exception class
(TRY003)
🤖 Prompt for AI Agents
In src/models/emotion_detection/samo_bert_emotion_classifier.py around lines
366-411, the EmotionDataset (constructor) never sets self.num_emotions but later
references it in __getitem__, causing a runtime error; update the __init__
signature to accept num_emotions: int = 28, store it as self.num_emotions, and
change the tokenizer type hint to the more generic PreTrainedTokenizerBase (or
Tokenizer base used across the project) for broader compatibility; then update
all call sites of EmotionDataset(...) to pass num_emotions where appropriate or
rely on the new default of 28.
| return EmotionDetectionResponse( | ||
| emotions=results["emotions"][0] | ||
| if results["emotions"] else [], | ||
| probabilities=results["probabilities"][0] | ||
| if results["probabilities"] else [], | ||
| predictions=results["predictions"][0] | ||
| if results["predictions"] else [], | ||
| processing_time=processing_time, | ||
| model_info={ | ||
| "model_name": "SAMO BERT Emotion Classifier", | ||
| "num_emotions": 28, | ||
| "device": str(self.models["emotion_detector"].device) | ||
| } | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Schema mismatch: cast predictions to int to satisfy Pydantic model.
predict_emotions returns float 0/1; your response model expects List[int].
return EmotionDetectionResponse(
emotions=results["emotions"][0]
if results["emotions"] else [],
- probabilities=results["probabilities"][0]
+ probabilities=results["probabilities"][0]
if results["probabilities"] else [],
- predictions=results["predictions"][0]
- if results["predictions"] else [],
+ predictions=[int(x) for x in results["predictions"][0]]
+ if results["predictions"] else [],
processing_time=processing_time,
model_info={📝 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 EmotionDetectionResponse( | |
| emotions=results["emotions"][0] | |
| if results["emotions"] else [], | |
| probabilities=results["probabilities"][0] | |
| if results["probabilities"] else [], | |
| predictions=results["predictions"][0] | |
| if results["predictions"] else [], | |
| processing_time=processing_time, | |
| model_info={ | |
| "model_name": "SAMO BERT Emotion Classifier", | |
| "num_emotions": 28, | |
| "device": str(self.models["emotion_detector"].device) | |
| } | |
| ) | |
| return EmotionDetectionResponse( | |
| emotions=results["emotions"][0] | |
| if results["emotions"] else [], | |
| probabilities=results["probabilities"][0] | |
| if results["probabilities"] else [], | |
| predictions=[int(x) for x in results["predictions"][0]] | |
| if results["predictions"] else [], | |
| processing_time=processing_time, | |
| model_info={ | |
| "model_name": "SAMO BERT Emotion Classifier", | |
| "num_emotions": 28, | |
| "device": str(self.models["emotion_detector"].device) | |
| } | |
| ) |
🤖 Prompt for AI Agents
In src/models/unified_api_server.py around lines 328 to 341, the response builds
EmotionDetectionResponse but predictions are floats (0.0/1.0) while the Pydantic
model expects List[int]; cast the prediction values to int before returning:
when results["predictions"] exists, map int over results["predictions"][0]
(e.g., [int(x) for x in results["predictions"][0]]) and return an empty list
when not present, keeping the same indexing/empty checks and leaving other
fields unchanged.
| @staticmethod | ||
| def test_detect_emotions_edge_cases(client): | ||
| """Test emotion detection with edge-case inputs.""" | ||
| # Test ambiguous text (multiple emotions) | ||
| ambiguous_text = "I'm feeling both excited and nervous about this opportunity, but also a bit sad to leave my current job." | ||
|
|
||
| response = client.post("/detect-emotions", json={ | ||
| "text": ambiguous_text, | ||
| "threshold": 0.3 | ||
| }) | ||
|
|
||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert len(data["emotions"]) > 1 # Should detect multiple emotions | ||
|
|
||
| # Test very short text | ||
| short_text = "Happy!" | ||
| response = client.post("/detect-emotions", json={ | ||
| "text": short_text, | ||
| "threshold": 0.3 | ||
| }) | ||
|
|
||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert "emotions" in data | ||
|
|
||
| # Test text with mixed emotions | ||
| mixed_text = "I love this but hate that. I'm excited yet anxious. Joy and fear together." | ||
| response = client.post("/detect-emotions", json={ | ||
| "text": mixed_text, | ||
| "threshold": 0.2 | ||
| }) | ||
|
|
||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert len(data["emotions"]) >= 2 # Should detect multiple conflicting emotions | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Edge-case emotions test: inject stub to avoid 503 and ensure multi-emotion outputs.
Same rationale—explicitly set a stub with multiple emotions.
- @staticmethod
- def test_detect_emotions_edge_cases(client):
+ def test_detect_emotions_edge_cases(self, api_server, client):
@@
- response = client.post("/detect-emotions", json={
+ from unittest.mock import Mock
+ emo = Mock()
+ emo.device = "cpu"
+ emo.predict_emotions.return_value = {
+ "emotions": [["excitement", "nervousness", "sadness"]],
+ "probabilities": [[0.8, 0.6, 0.35]],
+ "predictions": [[1, 1, 1]]
+ }
+ api_server.models["emotion_detector"] = emo
+
+ response = client.post("/detect-emotions", json={
"text": ambiguous_text,
"threshold": 0.3
})📝 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.
| @staticmethod | |
| def test_detect_emotions_edge_cases(client): | |
| """Test emotion detection with edge-case inputs.""" | |
| # Test ambiguous text (multiple emotions) | |
| ambiguous_text = "I'm feeling both excited and nervous about this opportunity, but also a bit sad to leave my current job." | |
| response = client.post("/detect-emotions", json={ | |
| "text": ambiguous_text, | |
| "threshold": 0.3 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data["emotions"]) > 1 # Should detect multiple emotions | |
| # Test very short text | |
| short_text = "Happy!" | |
| response = client.post("/detect-emotions", json={ | |
| "text": short_text, | |
| "threshold": 0.3 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "emotions" in data | |
| # Test text with mixed emotions | |
| mixed_text = "I love this but hate that. I'm excited yet anxious. Joy and fear together." | |
| response = client.post("/detect-emotions", json={ | |
| "text": mixed_text, | |
| "threshold": 0.2 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data["emotions"]) >= 2 # Should detect multiple conflicting emotions | |
| def test_detect_emotions_edge_cases(self, api_server, client): | |
| """Test emotion detection with edge-case inputs.""" | |
| # Test ambiguous text (multiple emotions) | |
| ambiguous_text = "I'm feeling both excited and nervous about this opportunity, but also a bit sad to leave my current job." | |
| from unittest.mock import Mock | |
| emo = Mock() | |
| emo.device = "cpu" | |
| emo.predict_emotions.return_value = { | |
| "emotions": [["excitement", "nervousness", "sadness"]], | |
| "probabilities": [[0.8, 0.6, 0.35]], | |
| "predictions": [[1, 1, 1]] | |
| } | |
| api_server.models["emotion_detector"] = emo | |
| response = client.post("/detect-emotions", json={ | |
| "text": ambiguous_text, | |
| "threshold": 0.3 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data["emotions"]) > 1 # Should detect multiple emotions | |
| # Test very short text | |
| short_text = "Happy!" | |
| response = client.post("/detect-emotions", json={ | |
| "text": short_text, | |
| "threshold": 0.3 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "emotions" in data | |
| # Test text with mixed emotions | |
| mixed_text = "I love this but hate that. I'm excited yet anxious. Joy and fear together." | |
| response = client.post("/detect-emotions", json={ | |
| "text": mixed_text, | |
| "threshold": 0.2 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data["emotions"]) >= 2 # Should detect multiple conflicting emotions |
🤖 Prompt for AI Agents
In tests/test_unified_api_server.py around lines 128 to 164, the edge-case
emotion tests call the real detection endpoint which can return 503 or a single
emotion; replace that call with a deterministic stub/mocked detector that
returns multiple emotions for the ambiguous/mixed inputs and a valid response
for the short text. Modify the test to patch the server's emotion-detection
function or client (using monkeypatch/pytest fixture or unittest.mock.patch)
before making POST requests so the endpoint returns a 200 with a predefined
payload containing multiple emotions (e.g., ["excited","nervous","sad"] for
ambiguous, at least two emotions for mixed, and a valid emotions list for short
text), then assert against those deterministic responses.
| @staticmethod | ||
| def test_detect_emotions_endpoint_validation(client): | ||
| """Test emotion detection endpoint validation.""" | ||
| # Test empty text | ||
| response = client.post("/detect-emotions", json={"text": ""}) | ||
| assert response.status_code == 422 | ||
|
|
||
| # Test invalid threshold | ||
| response = client.post("/detect-emotions", json={ | ||
| "text": "Test text", | ||
| "threshold": 1.5 # Invalid threshold | ||
| }) | ||
| assert response.status_code == 422 | ||
|
|
||
| # Test maximum allowed text length (10,000 characters) | ||
| long_text = "a" * 10000 | ||
| response = client.post("/detect-emotions", json={"text": long_text}) | ||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert "emotions" in data | ||
| assert "probabilities" in data | ||
| assert "predictions" in data | ||
| assert "processing_time" in data | ||
| assert "model_info" in data | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Validation test mixes 422 and a success path; ensure model is set before 10k-char case.
The last case expects 200, which requires a loaded model.
- @staticmethod
- def test_detect_emotions_endpoint_validation(client):
+ def test_detect_emotions_endpoint_validation(self, api_server, client):
@@
- response = client.post("/detect-emotions", json={"text": long_text})
+ # Provide a minimal stub to allow success
+ from unittest.mock import Mock
+ emo = Mock()
+ emo.device = "cpu"
+ emo.predict_emotions.return_value = {
+ "emotions": [["neutral"]],
+ "probabilities": [[0.51]],
+ "predictions": [[1]]
+ }
+ api_server.models["emotion_detector"] = emo
+ response = client.post("/detect-emotions", json={"text": long_text})📝 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.
| @staticmethod | |
| def test_detect_emotions_endpoint_validation(client): | |
| """Test emotion detection endpoint validation.""" | |
| # Test empty text | |
| response = client.post("/detect-emotions", json={"text": ""}) | |
| assert response.status_code == 422 | |
| # Test invalid threshold | |
| response = client.post("/detect-emotions", json={ | |
| "text": "Test text", | |
| "threshold": 1.5 # Invalid threshold | |
| }) | |
| assert response.status_code == 422 | |
| # Test maximum allowed text length (10,000 characters) | |
| long_text = "a" * 10000 | |
| response = client.post("/detect-emotions", json={"text": long_text}) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "emotions" in data | |
| assert "probabilities" in data | |
| assert "predictions" in data | |
| assert "processing_time" in data | |
| assert "model_info" in data | |
| def test_detect_emotions_endpoint_validation(self, api_server, client): | |
| """Test emotion detection endpoint validation.""" | |
| # Test empty text | |
| response = client.post("/detect-emotions", json={"text": ""}) | |
| assert response.status_code == 422 | |
| # Test invalid threshold | |
| response = client.post("/detect-emotions", json={ | |
| "text": "Test text", | |
| "threshold": 1.5 # Invalid threshold | |
| }) | |
| assert response.status_code == 422 | |
| # Test maximum allowed text length (10,000 characters) | |
| long_text = "a" * 10000 | |
| # Provide a minimal stub to allow success | |
| from unittest.mock import Mock | |
| emo = Mock() | |
| emo.device = "cpu" | |
| emo.predict_emotions.return_value = { | |
| "emotions": [["neutral"]], | |
| "probabilities": [[0.51]], | |
| "predictions": [[1]] | |
| } | |
| api_server.models["emotion_detector"] = emo | |
| response = client.post("/detect-emotions", json={"text": long_text}) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "emotions" in data | |
| assert "probabilities" in data | |
| assert "predictions" in data | |
| assert "processing_time" in data | |
| assert "model_info" in data |
🤖 Prompt for AI Agents
In tests/test_unified_api_server.py around lines 165 to 189, the validation test
sends two invalid requests (expecting 422) and then a long-text request
expecting 200, but the test never ensures a model is loaded before that success
path; update the test to load or set a valid model on the server (e.g., call the
model-selection/initialization endpoint or configure the test client/app to have
a model) before posting the 10,000-char payload so the final request can
legitimately return 200 and the assertions on
emotions/probabilities/predictions/processing_time/model_info succeed.
There was a problem hiding this comment.
Actionable comments posted: 38
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/testing/scientific_cloud_run_testing.py (1)
451-495: Make main runnable: read CLOUD_RUN_URL and execute full test plan.Currently it only prints instructions; monitor can't use it.
def main(): - """Main scientific testing execution.""" - print("🔬 SCIENTIFIC CLOUD RUN TESTING FRAMEWORK") - print("=" * 60) - - # Configuration - will be updated when user provides the Cloud Run URL - config = TestConfig( - cloud_run_url="https://YOUR-CLOUD-RUN-URL", # To be updated - num_runs=10, - num_concurrent=5, - timeout_seconds=30, - confidence_level=0.95 - ) - - tester = ScientificCloudRunTester(config) - - print("⚠️ WAITING FOR CLOUD RUN DEPLOYMENT TO COMPLETE...") - print("📝 Once deployed, update the cloud_run_url in the config above") - print("🚀 Then run comprehensive scientific testing") - print() - print("🎯 TEST PLAN:") - print(" 1. Comprehensive API testing (10 runs × 5 entries)") - print(" 2. Load testing (20 concurrent requests)") - print(" 3. Reliability testing (50 iterations)") - print(" 4. Statistical analysis with confidence intervals") - print(" 5. Performance benchmarking") - print() - print("📊 METRICS TO MEASURE:") - print(" • Success rate with confidence intervals") - print(" • Latency percentiles (P50, P95, P99)") - print(" • Throughput (requests/second)") - print(" • Error patterns and failure modes") - print(" • Consistency across multiple runs") - print() - print("🔬 SCIENTIFIC METHODOLOGY:") - print(" • Controlled test data (consistent inputs)") - print(" • Multiple runs for statistical significance") - print(" • Error handling and edge case testing") - print(" • Performance benchmarking under load") - print(" • Reliability assessment over time") + """Main scientific testing execution.""" + print("🔬 SCIENTIFIC CLOUD RUN TESTING FRAMEWORK") + print("=" * 60) + + url = os.getenv("CLOUD_RUN_URL", "https://YOUR-CLOUD-RUN-URL") + if "YOUR-CLOUD-RUN-URL" in url: + print("⚠️ CLOUD_RUN_URL not set. Export CLOUD_RUN_URL and rerun.") + return + + config = TestConfig( + cloud_run_url=url, + num_runs=10, + num_concurrent=5, + timeout_seconds=30, + confidence_level=0.95 + ) + tester = ScientificCloudRunTester(config) + + # 1) Comprehensive suite + results = tester.run_comprehensive_test_suite() + # 2) Load test + load = tester.run_load_test(concurrent_requests=20) + # 3) Reliability + reliability = tester.run_reliability_test(num_iterations=50) + + # Aggregate and persist + bundle = { + "suite": results, + "load": load, + "reliability": reliability, + "cloud_run_url": url, + "timestamp": time.time(), + } + tester.save_results(bundle)
♻️ Duplicate comments (15)
src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
268-276: Use canonical emotion labels instead of hardcoded list.Avoid duplicating label order; import from the shared mapping to keep API and training consistent.
Apply this diff:
- # Define descriptive emotion labels (28 emotions) - emotion_labels = [ - "admiration", "amusement", "anger", "annoyance", "approval", "caring", - "confusion", "curiosity", "desire", "disappointment", "disapproval", - "disgust", "embarrassment", "excitement", "fear", "gratitude", "grief", - "joy", "love", "nervousness", "optimism", "pride", "realization", - "relief", "remorse", "sadness", "surprise", "neutral" - ] + # Use canonical GoEmotions label order + from .emotion_labels import get_all_emotions + emotion_labels = get_all_emotions()scripts/testing/quick_model_test.py (1)
15-18: Path injection is brittle. Prefer package install or PYTHONPATH.For a dev-only script it’s fine; otherwise consider
pip install -e .or documentPYTHONPATH.test_samo_emotion_detection_standalone.py (1)
12-16: Avoid sys.path manipulation in tests.Prefer running tests with the project as a package or via PYTHONPATH; this is fragile on CI.
-# Add src to path -sys.path.insert(0, str(Path(__file__).parent / "src")) +# Prefer: export PYTHONPATH="${PYTHONPATH}:$(git rev-parse --show-toplevel)/src" +# or install in editable mode: pip install -e .scripts/start_api_server.py (2)
15-17: Path injection is brittle.Same recommendation as other scripts: prefer editable install or PYTHONPATH over
sys.pathmanipulation.
105-107: Honor--workersand--reload.Either extend
SAMOUnifiedAPIServer.runor run uvicorn here. Quick fix shown below.- # Start the server - server.run(host=args.host, port=args.port) + # Start the server with workers/reload + import uvicorn + uvicorn.run(server.app, host=args.host, port=args.port, workers=args.workers, reload=args.reload)If you prefer to keep
server.run(...), I can send a PR to addworkers/reloadparameters insrc/models/unified_api_server.py.tests/test_unified_api_server.py (3)
179-189: Good addition of maximum text length testThis properly addresses the previous review comment about testing the 10,000 character limit.
205-212: Test for empty audio content added as requestedGood implementation of the test for valid audio file type with empty content, addressing the previous review comment.
329-357: Fix incorrect model access patternThe test incorrectly accesses models through
client.app.state.models, but based on the server implementation, models are stored as instance variables (self.models), not inapp.state.- def test_model_unavailable_errors(self, api_server, client): + def test_model_unavailable_errors(self, api_server, client): """Test error handling when models are not available.""" import copy # Temporarily set models to None - original_models = copy.deepcopy(api_server.models) + original_models = copy.deepcopy(api_server.models) try: # Mock unavailable models - api_server.models = { + api_server.models = { "summarizer": None, "transcriber": None, "emotion_detector": None } # Test summarization response = client.post("/summarize", json={"text": "Test text"}) assert response.status_code == 503 assert "not available" in response.json()["detail"] # Test emotion detection response = client.post("/detect-emotions", json={"text": "Test text"}) assert response.status_code == 503 assert "not available" in response.json()["detail"] finally: # Restore original models - api_server.models = original_models + api_server.models = original_modelsdeployment/cloud-run/secure_api_server.py (2)
259-461: Resource classes referenced but not definedThe code registers resource classes (Health, Predict, PredictBatch, Emotions, ModelStatus, SecurityStatus) with Flask-RESTX, but these classes are not defined in this file. The actual API logic is defined as decorated methods within the namespace.
The resource registration at lines 456-461 appears to be unnecessary since the routes are already defined via decorators (e.g.,
@main_ns.route('/health')). Either:
- Remove the redundant
add_resourcecalls- Or refactor to use proper Resource classes
-# Register resources with namespaces (after class definitions) -main_ns.add_resource(Health, '/health') -main_ns.add_resource(Predict, '/predict') -main_ns.add_resource(PredictBatch, '/predict/batch') -main_ns.add_resource(Emotions, '/emotions') -admin_ns.add_resource(ModelStatus, '/model/status') -admin_ns.add_resource(SecurityStatus, '/security/status')
414-431: Route duplication detectedModelStatus and SecurityStatus are defined with routes
/model_statusand/security_statusvia decorators, but then registered again with different paths/model/statusand/security/statusviaadd_resource(). This creates duplicate and conflicting routes.Since the classes use decorator-based routing, remove the redundant
add_resourcecalls:-admin_ns.add_resource(ModelStatus, '/model/status') -admin_ns.add_resource(SecurityStatus, '/security/status')Also applies to: 432-454
src/models/unified_api_server.py (5)
147-151: Good CORS security implementationExcellent work implementing environment-based CORS configuration to address the previous security concern. This allows proper origin restriction in production.
251-271: Excellent MIME type validation implementationGreat implementation of proper MIME type validation using python-magic library as suggested in the previous review. This properly addresses the security concern about file extension validation.
274-281: Good security fix for temporary file handlingExcellent implementation using UUID for unique temporary filenames to prevent race conditions and security issues as previously suggested.
490-494: Add support for workers and reload parametersThe
runmethod should acceptworkersandreloadparameters to match usage instart_api_server.py.- def run(self, host: str = "0.0.0.0", port: int = 8000): + def run(self, host: str = "0.0.0.0", port: int = 8000, workers: int = 1, reload: bool = False): """Run the API server.""" - logger.info(f"Starting SAMO Unified API Server on {host}:{port}") - uvicorn.run(self.app, host=host, port=port) + logger.info(f"Starting SAMO Unified API Server on {host}:{port} with {workers} worker(s)") + uvicorn.run(self.app, host=host, port=port, workers=workers, reload=reload)
369-376: Missing UUID import in combined processingThe code uses
uuid.uuid4()at line 370 butuuidis not imported earlier in the file.Add the missing import at the top of the file:
import time +import uuid from pathlib import Path
🧹 Nitpick comments (45)
scripts/testing/quick_deployment_check.py (2)
9-13: Missing import for API key usageAdd os import for reading SAMO_API_KEY.
import sys import subprocess import requests +import os from pathlib import Path
81-86: Handle missing gcloud CLI explicitlyImprove error message when Cloud SDK isn’t installed or not in PATH.
+ except FileNotFoundError: + print("❌ gcloud CLI not found. Install Google Cloud SDK or add it to PATH.") + return False except subprocess.TimeoutExpired: print("⏱️ Command timed out") return Falsescripts/testing/deberta_simple_test.py (1)
60-67: Print actual prediction instead of a placeholderRemoves dead code warning and prints label with score.
- top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} - print(f"Text: {text}") - print(".3f") - print() + top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} + print(f"Text: {text}") + print(f"Prediction: {top_emotion['label']} ({top_emotion.get('score', 0.0):.3f})\n")scripts/testing/comprehensive_journal_inference_report.md (1)
1-7: Clarify scope vs this PR’s 28-class DeBERTaThis report evaluates a 12-emotion RoBERTa model, while this PR deploys a 28-emotion DeBERTa model. Add a brief scope note to avoid confusion.
# 🎯 COMPREHENSIVE JOURNAL INFERENCE DEMO REPORT ## Executive Summary + +> Scope note: This report covers the 12-emotion RoBERTa variant used for journaling experiments. The production deployment in this PR targets a 28-emotion DeBERTa model, so metrics and categories will differ.scripts/testing/debug_deberta_loading.py (4)
54-64: Print actual load/inference timings instead of placeholders (Method 1)Makes collected timings visible; removes dead-variable warnings.
- print(".2f") + print(f"⏱️ Load time: {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference time: {inference_time:.3f}s")
119-141: Print actual load/inference timings (Method 2)Same improvement for Method 2.
- print(".2f") + print(f"⏱️ Load time: {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference time: {inference_time:.3f}s")
170-199: Print actual download/load/inference timings; consider downloading weightsCurrently large weight files are skipped, which undermines the “pre-download” goal. Either remove ignore_patterns or add a second download for safetensors.
- print(".2f") + print(f"⬇️ Download time: {download_time:.2f}s") @@ - print(".2f") + print(f"⏱️ Load time: {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference time: {inference_time:.3f}s")Optional snapshot_download adjustment (if you want full offline readiness):
- local_dir = snapshot_download( + local_dir = snapshot_download( repo_id=model_name, local_dir=cache_dir, local_dir_use_symlinks=False, - ignore_patterns=["*.bin", "*.safetensors"] # Skip large files first + # Download all files including weights to enable offline loading + ignore_patterns=[] )
158-166: Use a secure temp directory for cachingAvoid hardcoding /tmp paths; use tempfile.mkdtemp() to reduce collisions and TOCTOU issues.
I can provide a small helper to manage the temp dir lifecycle if you want.
scripts/testing/test_deberta_api.py (2)
1-1: File permissions mismatch with shebangThe file has a shebang but is not executable. Either make the file executable or remove the shebang.
-#!/usr/bin/env python3Or alternatively, make the file executable:
chmod +x scripts/testing/test_deberta_api.py
38-40: Consider usinglogging.exceptionfor better error trackingWhen catching exceptions,
logging.exceptionprovides automatic traceback inclusion.except Exception as e: - logger.error(f"❌ API health check error: {e}") + logger.exception("❌ API health check error") return Falsescripts/testing/deberta_workaround.py (2)
1-1: File permissions mismatch with shebangThe file has a shebang but is not executable.
-#!/usr/bin/env python3
51-53: Avoid catching generic ExceptionCatching generic exceptions can mask unexpected errors. Consider catching specific exceptions from
hf_hub_download.- except Exception as e: + except (OSError, ValueError, RuntimeError) as e: print(f"❌ Failed to download {filename}: {e}") return Nonedeployment/cloud-run/model_utils.py (3)
315-315: Remove unused variable assignmentThe
max_lengthvariable is assigned but never used.model_name = PRODUCTION_MODEL_NAME model_kwargs = {"torch_dtype": "float32"} - max_length = 512 model_type = "Production (6 emotions)"
178-179: Redundant exception in logging.exception callThe exception object is already included when using
logging.exception.except Exception as e: - logger.exception("DeBERTa prediction failed: %s", e) + logger.exception("DeBERTa prediction failed") raise
534-534: Redundant exception in logging.exception callThe exception object is already included when using
logging.exception.- logger.exception("DeBERTa batch prediction failed for text: %s", e) + logger.exception("DeBERTa batch prediction failed for text")scripts/testing/test_deberta_isolated.py (2)
1-1: File permissions mismatch with shebangThe file has a shebang but is not executable.
-#!/usr/bin/env python3
71-71: Redundant exception in logging.exception callThe exception object is already included when using
logging.exception.- logger.exception(f"❌ DeBERTa isolated test failed: {e}") + logger.exception("❌ DeBERTa isolated test failed")scripts/deployment/deploy_deberta_model.py (3)
1-1: File permissions mismatch with shebangThe file has a shebang but is not executable.
-#!/usr/bin/env python3
103-105: Avoid catching generic ExceptionConsider catching specific exceptions from the transformers library.
- except Exception as e: + except (ImportError, RuntimeError, ValueError) as e: print(f"❌ DeBERTa loading failed: {e}") return False
137-225: Consider extracting deployment instructions to a separate fileThe inline documentation string is very long and would be better maintained as a separate markdown template file.
Would you like me to help extract this to a separate template file that can be more easily maintained and version controlled?
scripts/testing/test_model_switching.py (1)
44-51: Optional: narrow exception and surface errors.Catching bare Exception hides actionable errors in tests.
- except Exception as e: + except (KeyError, TypeError, ValueError) as e: print(f"❌ Prediction failed: {e}")scripts/testing/comprehensive_journal_inference_demo.py (4)
15-16: Type hints: use Optional for None default.Minor typing nit.
-from typing import List, Dict, Any +from typing import List, Dict, Any, Optional
24-26: Annotate Optional in constructor.- def __init__(self, model_path: str = None): + def __init__(self, model_path: Optional[str] = None):
106-110: Guard against index overflow when labels and model dims diverge.Prevents IndexError if logits > label list.
- if pred: # Only include emotions above threshold - predicted_emotions.append(self.emotion_labels[i]) - emotion_scores.append(float(prob)) + if pred: # Only include emotions above threshold + label = self.emotion_labels[i] if i < len(self.emotion_labels) else f"emotion_{i}" + predicted_emotions.append(label) + emotion_scores.append(float(prob))
268-270: Avoid printing a fixed filename before saving.This can mislead since save_results adds a timestamp.
- print("\n✅ Demo completed successfully!") - print("📁 Results saved to: comprehensive_journal_demo_results.json") + print("\n✅ Demo completed successfully!") + print("📁 Use save_results(results) to persist with a timestamped filename.")scripts/testing/deberta_journal_inference_demo.py (4)
15-17: Type hints: import Optional.-from typing import List, Dict, Any +from typing import List, Dict, Any, Optional
25-27: Constructor signature nit.- def __init__(self, model_name: str = 'duelker/samo-goemotions-deberta-v3-large'): + def __init__(self, model_name: Optional[str] = 'duelker/samo-goemotions-deberta-v3-large'):
124-131: Optional: return id2label-probabilities map for easier debugging.Small usability boost.
- return { + return { "primary_emotion": primary_emotion, "confidence": primary_confidence, "predicted_emotions": predicted_emotions, "emotion_scores": emotion_scores, "all_probabilities": {str(i): float(prob) for i, prob in enumerate(probabilities)}, "processing_time_ms": 0.0 # Will be set by caller }Consider also emitting a dict keyed by emotion names when id2label is present.
303-315: Type hints: Optional for filename.- def save_results(results: Dict[str, Any], filename: str = None) -> str: + def save_results(results: Dict[str, Any], filename: Optional[str] = None) -> str:src/models/emotion_detection/samo_bert_emotion_classifier.py (3)
201-208: Typing: makethresholdOptional.PEP 484:
threshold: float = Noneshould beOptional[float].- threshold: float = None, + threshold: Optional[float] = None,
293-297: Avoid.datamutation when setting temperature.Use no_grad context to prevent autograd graph misuse.
- self.temperature.data.fill_(temperature) - logger.info(f"Set temperature to {temperature}") + import math + with torch.no_grad(): + self.temperature.fill_(float(temperature)) + logger.info(f"Set temperature to {float(temperature):.3f}")
29-35: Library hygiene: don’t set global logging/warnings in a model module.Avoid
logging.basicConfigand global warning suppression in library code; leave it to apps.-# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Suppress warnings for cleaner output -warnings.filterwarnings("ignore", category=UserWarning) +# Module logger (configuration should be done by the application) +logger = logging.getLogger(__name__)test_samo_emotion_detection_standalone.py (2)
99-107: Use f-string conversion flag for repr and narrow exception where possible.Cleaner formatting and less broad exception handling in tests.
- print(f"\n Invalid input {i}: {type(invalid_input).__name__} = {repr(invalid_input)}") + print(f"\n Invalid input {i}: {type(invalid_input).__name__} = {invalid_input!r}") @@ - except Exception as e: + except (TypeError, ValueError) as e: print(f" Expected error: {type(e).__name__}: {e}")
228-276: Gate the heavy batch performance test to keep CI fast.1,000+ texts can be slow on shared runners. Make it opt-in via env var.
- try: + try: + if os.getenv("RUN_SLOW_TESTS") != "1": + print("⏭️ Skipping batch performance (set RUN_SLOW_TESTS=1 to enable)") + return model, _ = create_samo_bert_emotion_classifier()deployment/cloud-run/cloudbuild.yaml (2)
3-3: Add a content-addressable/tagged image for traceability.Tag the image with the commit SHA alongside the latest tag to enable rollbacks.
- args: ['build', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta', '-f', 'deployment/cloud-run/Dockerfile.deberta', '.'] + args: ['build', + '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta', + '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$SHORT_SHA', + '-f', 'deployment/cloud-run/Dockerfile.deberta', '.']
5-5: Push both tags.Ensure Cloud Build pushes the SHA tag too.
- - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta' + - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta' + - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$SHORT_SHA'scripts/start_api_server.py (1)
108-113: Uselogger.exceptionto preserve traceback.Keeps error details in logs.
- except Exception as e: - logger.error(f"❌ Failed to start server: {e}") + except Exception as e: + logger.exception("❌ Failed to start server") sys.exit(1)tests/test_unified_api_server.py (1)
1-1: Remove shebang from non-executable test fileThe shebang line is present but the file is not marked as executable. Test files are typically run through pytest rather than directly executed.
-#!/usr/bin/env python3deployment/cloud-run/secure_api_server.py (1)
90-90: Trailing whitespaceRemove trailing whitespace for cleaner code.
-src/models/unified_api_server.py (2)
1-1: Remove shebang from library moduleThe shebang is unnecessary since this is imported as a module rather than executed directly.
-#!/usr/bin/env python3
410-410: Use f-string for consistencyReplace string concatenation with f-string for consistency with the rest of the codebase.
- summary=transcription_result.text[:200] + "...", + summary=f"{transcription_result.text[:200]}...",scripts/testing/model_comparison_test.py (4)
1-1: Remove unnecessary shebangThe shebang is present but the file is not marked as executable.
-#!/usr/bin/env python3
169-182: Remove unused loop variableThe loop variable
iis not used within the loop body.- for i in range(num_runs): + for _ in range(num_runs):
249-249: Add explicit Optional type hintsPEP 484 prohibits implicit Optional. Parameters that can be None should use Optional[T].
- def run_comprehensive_benchmark(self, test_texts: List[str] = None, test_data: List[Dict[str, Any]] = None) -> Dict[str, Any]: + def run_comprehensive_benchmark(self, test_texts: Optional[List[str]] = None, test_data: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
390-397: Use logging.exception for error loggingWhen logging exceptions in an except block, use
logging.exceptionto include the traceback.except Exception as e: - logger.error(f"❌ Benchmark failed: {e}") + logger.exception("❌ Benchmark failed") print("💡 Troubleshooting tips:")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
src/models/__pycache__/__init__.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/__init__.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/config.cpython-38.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/enhanced_bert_classifier.cpython-38.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/samo_bert_emotion_classifier.cpython-310.pycis excluded by!**/*.pycsrc/models/emotion_detection/__pycache__/samo_bert_emotion_classifier.cpython-312.pycis excluded by!**/*.pyc
📒 Files selected for processing (25)
deployment/cloud-run/cloudbuild.yaml(1 hunks)deployment/cloud-run/model_utils.py(7 hunks)deployment/cloud-run/secure_api_server.py(4 hunks)push_log.txt(1 hunks)scripts/deployment/deploy_deberta_model.py(1 hunks)scripts/start_api_server.py(1 hunks)scripts/testing/cloud_run_deployment_monitor.py(1 hunks)scripts/testing/comprehensive_journal_inference_demo.py(1 hunks)scripts/testing/comprehensive_journal_inference_report.md(1 hunks)scripts/testing/deberta_journal_inference_demo.py(1 hunks)scripts/testing/deberta_safetensors_test.py(1 hunks)scripts/testing/deberta_simple_test.py(1 hunks)scripts/testing/deberta_workaround.py(1 hunks)scripts/testing/debug_deberta_loading.py(1 hunks)scripts/testing/model_comparison_test.py(1 hunks)scripts/testing/quick_deployment_check.py(1 hunks)scripts/testing/quick_model_test.py(1 hunks)scripts/testing/scientific_cloud_run_testing.py(1 hunks)scripts/testing/test_deberta_api.py(1 hunks)scripts/testing/test_deberta_isolated.py(1 hunks)scripts/testing/test_model_switching.py(1 hunks)src/models/emotion_detection/samo_bert_emotion_classifier.py(1 hunks)src/models/unified_api_server.py(1 hunks)test_samo_emotion_detection_standalone.py(1 hunks)tests/test_unified_api_server.py(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- push_log.txt
🧰 Additional context used
🧬 Code graph analysis (20)
scripts/testing/test_deberta_isolated.py (1)
deployment/cloud-run/model_utils.py (3)
ensure_model_loaded(223-384)predict_emotions(387-464)get_model_status(467-482)
scripts/testing/scientific_cloud_run_testing.py (2)
deployment/cloud-run/secure_api_server.py (2)
post(299-335)post(348-393)scripts/testing/cloud_run_deployment_monitor.py (1)
main(186-224)
scripts/testing/deberta_safetensors_test.py (3)
scripts/testing/deberta_simple_test.py (2)
compare_with_production(80-98)main(100-114)scripts/testing/deberta_workaround.py (1)
main(151-158)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
scripts/testing/quick_model_test.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)
scripts/testing/test_model_switching.py (1)
deployment/cloud-run/model_utils.py (2)
ensure_model_loaded(223-384)predict_emotions(387-464)
scripts/testing/deberta_journal_inference_demo.py (3)
scripts/testing/comprehensive_journal_inference_demo.py (6)
load_model(58-79)predict_emotions(81-141)create_journal_entries(144-172)run_comprehensive_demo(174-271)save_results(274-286)main(289-314)deployment/cloud-run/model_utils.py (1)
predict_emotions(387-464)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
scripts/deployment/deploy_deberta_model.py (3)
scripts/testing/test_deberta_isolated.py (1)
test_deberta_loading(21-72)scripts/testing/deberta_safetensors_test.py (1)
main(138-163)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
scripts/testing/deberta_simple_test.py (2)
scripts/testing/deberta_safetensors_test.py (2)
compare_with_production(111-136)main(138-163)scripts/testing/debug_deberta_loading.py (1)
main(257-276)
scripts/testing/model_comparison_test.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)deployment/cloud-run/model_utils.py (1)
predict_emotions(387-464)
scripts/testing/quick_deployment_check.py (2)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)scripts/testing/cloud_run_deployment_monitor.py (1)
main(186-224)
scripts/testing/comprehensive_journal_inference_demo.py (2)
scripts/testing/deberta_journal_inference_demo.py (6)
load_model(47-74)predict_emotions(76-143)create_journal_entries(146-174)run_comprehensive_demo(176-300)save_results(303-315)main(318-344)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
scripts/testing/debug_deberta_loading.py (2)
scripts/deployment/deploy_deberta_model.py (1)
main(231-265)scripts/testing/model_comparison_test.py (1)
main(369-396)
scripts/testing/cloud_run_deployment_monitor.py (3)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)scripts/testing/test_deberta_api.py (1)
main(154-196)scripts/testing/quick_deployment_check.py (1)
main(89-111)
scripts/testing/test_deberta_api.py (1)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)
src/models/unified_api_server.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(424-464)predict_emotions(201-291)
deployment/cloud-run/secure_api_server.py (5)
deployment/cloud-run/test_minimal_swagger.py (2)
get(34-35)Health(33-35)deployment/cloud-run/test_swagger_debug.py (2)
get(29-30)Health(28-30)deployment/cloud-run/test_routing_minimal.py (2)
get(29-30)Health(28-30)deployment/cloud-run/test_swagger_no_model.py (2)
get(41-42)Health(40-42)deployment/cloud-run/test_routing_debug.py (2)
get(37-38)Health(36-38)
src/models/emotion_detection/samo_bert_emotion_classifier.py (4)
deployment/cloud-run/secure_api_server.py (4)
get(265-286)get(400-411)get(421-430)get(439-453)deployment/cloud-run/model_utils.py (1)
predict_emotions(387-464)scripts/testing/comprehensive_journal_inference_demo.py (1)
predict_emotions(81-141)scripts/testing/deberta_journal_inference_demo.py (1)
predict_emotions(76-143)
scripts/start_api_server.py (1)
src/models/unified_api_server.py (2)
SAMOUnifiedAPIServer(134-493)run(490-493)
test_samo_emotion_detection_standalone.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (5)
create_samo_bert_emotion_classifier(424-464)count_parameters(298-300)count_frozen_parameters(302-304)predict_emotions(201-291)set_temperature(293-296)src/models/emotion_detection/emotion_labels.py (2)
get_all_emotions(269-276)get_emotion_description(243-253)
tests/test_unified_api_server.py (2)
src/models/unified_api_server.py (1)
SAMOUnifiedAPIServer(134-493)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
🪛 Ruff (0.12.2)
scripts/testing/test_deberta_isolated.py
1-1: Shebang is present but file is not executable
(EXE001)
65-65: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
65-65: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
68-68: Consider moving this statement to an else block
(TRY300)
71-71: Redundant exception object included in logging.exception call
(TRY401)
scripts/testing/scientific_cloud_run_testing.py
1-1: Shebang is present but file is not executable
(EXE001)
199-199: SyntaxError: Expected ',', found name
202-202: SyntaxError: Expected ',', found name
205-205: SyntaxError: Expected ',', found name
205-206: SyntaxError: Expected ')', found newline
222-222: SyntaxError: missing closing quote in string literal
223-223: SyntaxError: Got unexpected token 🔥
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found string
223-223: SyntaxError: Got unexpected token ⚡
223-223: SyntaxError: Expected ',', found name
223-223: SyntaxError: Expected ',', found ':'
223-223: SyntaxError: missing closing quote in string literal
226-226: SyntaxError: Expected ',', found name
228-228: SyntaxError: Expected ',', found name
228-229: SyntaxError: Expected ')', found newline
243-243: SyntaxError: Expected ',', found name
244-244: SyntaxError: Expected ',', found name
246-246: SyntaxError: Expected ',', found name
247-247: SyntaxError: Expected ',', found name
249-249: SyntaxError: Expected ',', found name
249-250: SyntaxError: Expected ')', found newline
265-265: SyntaxError: missing closing quote in string literal
266-266: SyntaxError: Got unexpected token 🔄
266-266: SyntaxError: Expected ',', found name
266-266: SyntaxError: Expected ',', found string
266-266: SyntaxError: Got unexpected token 📊
266-266: SyntaxError: Expected ',', found ':'
266-266: SyntaxError: missing closing quote in string literal
269-269: SyntaxError: Expected ',', found name
270-270: SyntaxError: Expected ',', found name
272-272: SyntaxError: Expected ',', found name
273-273: SyntaxError: Expected ',', found name
273-274: SyntaxError: Expected ')', found newline
361-361: SyntaxError: Expected ',', found name
362-362: SyntaxError: Expected ',', found name
363-363: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
364-364: SyntaxError: Expected ',', found name
365-365: SyntaxError: Expected ',', found name
366-366: SyntaxError: Expected ',', found name
366-366: SyntaxError: Expected ',', found name
367-367: SyntaxError: Expected ',', found name
368-368: SyntaxError: Expected ',', found name
369-369: SyntaxError: Expected ',', found name
369-369: SyntaxError: Expected ',', found name
370-370: SyntaxError: Expected ',', found name
372-372: SyntaxError: Expected ',', found name
372-373: SyntaxError: Expected ')', found NonLogicalNewline
scripts/testing/deberta_safetensors_test.py
1-1: Shebang is present but file is not executable
(EXE001)
61-61: Consider moving this statement to an else block
(TRY300)
63-63: Do not catch blind exception: Exception
(BLE001)
103-103: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
104-104: Local variable top_emotion is assigned to but never used
Remove assignment to unused variable top_emotion
(F841)
135-135: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
135-135: Loop control variable pred not used within loop body
Rename unused pred to _pred
(B007)
scripts/testing/deberta_workaround.py
1-1: Shebang is present but file is not executable
(EXE001)
24-24: Probable insecure usage of temporary file or directory: "/tmp/deberta_manual"
(S108)
51-51: Do not catch blind exception: Exception
(BLE001)
89-89: Consider moving this statement to an else block
(TRY300)
91-91: Do not catch blind exception: Exception
(BLE001)
scripts/testing/quick_model_test.py
1-1: Shebang is present but file is not executable
(EXE001)
48-48: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
62-62: Consider moving this statement to an else block
(TRY300)
64-64: Do not catch blind exception: Exception
(BLE001)
101-101: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
110-110: Local variable emotion is assigned to but never used
Remove assignment to unused variable emotion
(F841)
111-111: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
116-116: Consider moving this statement to an else block
(TRY300)
118-118: Do not catch blind exception: Exception
(BLE001)
147-147: Do not catch blind exception: Exception
(BLE001)
164-164: Do not catch blind exception: Exception
(BLE001)
186-186: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
195-195: Local variable emotion is assigned to but never used
Remove assignment to unused variable emotion
(F841)
196-196: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
201-201: Consider moving this statement to an else block
(TRY300)
203-203: Do not catch blind exception: Exception
(BLE001)
scripts/testing/test_model_switching.py
1-1: Shebang is present but file is not executable
(EXE001)
49-49: Do not catch blind exception: Exception
(BLE001)
84-84: Do not catch blind exception: Exception
(BLE001)
scripts/testing/deberta_journal_inference_demo.py
1-1: Shebang is present but file is not executable
(EXE001)
70-70: Consider moving this statement to an else block
(TRY300)
72-72: Do not catch blind exception: Exception
(BLE001)
133-133: Do not catch blind exception: Exception
(BLE001)
303-303: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
341-341: Do not catch blind exception: Exception
(BLE001)
scripts/deployment/deploy_deberta_model.py
1-1: Shebang is present but file is not executable
(EXE001)
101-101: Consider moving this statement to an else block
(TRY300)
103-103: Do not catch blind exception: Exception
(BLE001)
131-131: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
scripts/testing/deberta_simple_test.py
1-1: Shebang is present but file is not executable
(EXE001)
64-64: Local variable top_emotion is assigned to but never used
Remove assignment to unused variable top_emotion
(F841)
68-68: Do not catch blind exception: Exception
(BLE001)
71-71: Consider moving this statement to an else block
(TRY300)
73-73: Do not catch blind exception: Exception
(BLE001)
scripts/testing/model_comparison_test.py
1-1: Shebang is present but file is not executable
(EXE001)
66-66: Consider moving this statement to an else block
(TRY300)
67-67: Do not catch blind exception: Exception
(BLE001)
87-87: Do not catch blind exception: Exception
(BLE001)
110-110: Consider moving this statement to an else block
(TRY300)
111-111: Do not catch blind exception: Exception
(BLE001)
133-133: Consider moving this statement to an else block
(TRY300)
134-134: Do not catch blind exception: Exception
(BLE001)
169-169: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
176-176: Local variable results is assigned to but never used
Remove assignment to unused variable results
(F841)
226-226: Local variable confidence is assigned to but never used
Remove assignment to unused variable confidence
(F841)
249-249: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
249-249: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
391-391: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
scripts/testing/quick_deployment_check.py
1-1: Shebang is present but file is not executable
(EXE001)
65-65: SyntaxError: Expected ',', found name
66-66: SyntaxError: Expected ',', found name
67-67: SyntaxError: Expected ',', found name
68-68: SyntaxError: Expected ',', found name
68-69: SyntaxError: Expected ')', found newline
scripts/testing/comprehensive_journal_inference_demo.py
1-1: Shebang is present but file is not executable
(EXE001)
24-24: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
40-40: Avoid specifying long messages outside the exception class
(TRY003)
75-75: Consider moving this statement to an else block
(TRY300)
77-77: Do not catch blind exception: Exception
(BLE001)
131-131: Do not catch blind exception: Exception
(BLE001)
274-274: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
311-311: Do not catch blind exception: Exception
(BLE001)
scripts/testing/debug_deberta_loading.py
1-1: Shebang is present but file is not executable
(EXE001)
52-52: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
60-60: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
65-65: Consider moving this statement to an else block
(TRY300)
67-67: Do not catch blind exception: Exception
(BLE001)
89-89: Do not catch blind exception: Exception
(BLE001)
94-94: Do not catch blind exception: Exception
(BLE001)
106-106: Do not catch blind exception: Exception
(BLE001)
115-115: Do not catch blind exception: Exception
(BLE001)
119-119: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
137-137: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
142-142: Consider moving this statement to an else block
(TRY300)
144-144: Do not catch blind exception: Exception
(BLE001)
159-159: Probable insecure usage of temporary file or directory: "/tmp/deberta_cache"
(S108)
164-164: Local variable local_dir is assigned to but never used
Remove assignment to unused variable local_dir
(F841)
170-170: Local variable download_time is assigned to but never used
Remove assignment to unused variable download_time
(F841)
187-187: Local variable load_time is assigned to but never used
Remove assignment to unused variable load_time
(F841)
195-195: Local variable inference_time is assigned to but never used
Remove assignment to unused variable inference_time
(F841)
200-200: Consider moving this statement to an else block
(TRY300)
202-202: Do not catch blind exception: Exception
(BLE001)
230-230: Do not catch blind exception: Exception
(BLE001)
scripts/testing/cloud_run_deployment_monitor.py
1-1: Shebang is present but file is not executable
(EXE001)
33-33: subprocess call: check for execution of untrusted input
(S603)
40-40: Consider moving this statement to an else block
(TRY300)
45-45: Do not catch blind exception: Exception
(BLE001)
60-60: Consider moving this statement to an else block
(TRY300)
68-68: Do not catch blind exception: Exception
(BLE001)
94-94: Do not catch blind exception: Exception
(BLE001)
157-157: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF001)
169-169: Undefined name os
(F821)
173-173: subprocess call: check for execution of untrusted input
(S603)
182-182: Do not catch blind exception: Exception
(BLE001)
scripts/testing/test_deberta_api.py
1-1: Shebang is present but file is not executable
(EXE001)
36-36: Consider moving this statement to an else block
(TRY300)
38-38: Do not catch blind exception: Exception
(BLE001)
39-39: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
64-64: Unnecessary key check before dictionary access
Replace with dict.get
(RUF019)
66-66: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
66-66: SyntaxError: Cannot reuse outer quote character in f-strings on Python 3.8 (syntax was added in Python 3.12)
77-77: Consider moving this statement to an else block
(TRY300)
79-79: Do not catch blind exception: Exception
(BLE001)
80-80: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
106-106: Consider moving this statement to an else block
(TRY300)
108-108: Do not catch blind exception: Exception
(BLE001)
109-109: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
141-141: Unnecessary key check before dictionary access
Replace with dict.get
(RUF019)
148-148: Consider moving this statement to an else block
(TRY300)
150-150: Do not catch blind exception: Exception
(BLE001)
151-151: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
deployment/cloud-run/model_utils.py
75-75: Unused method argument: kwargs
(ARG002)
132-132: Avoid specifying long messages outside the exception class
(TRY003)
175-175: Consider moving this statement to an else block
(TRY300)
178-178: Redundant exception object included in logging.exception call
(TRY401)
288-288: Do not catch blind exception: Exception
(BLE001)
293-293: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
315-315: Local variable max_length is assigned to but never used
Remove assignment to unused variable max_length
(F841)
327-327: Do not catch blind exception: Exception
(BLE001)
332-332: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
366-366: Do not catch blind exception: Exception
(BLE001)
534-534: Redundant exception object included in logging.exception call
(TRY401)
src/models/unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
176-176: Do not catch blind exception: Exception
(BLE001)
177-177: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
185-185: Do not catch blind exception: Exception
(BLE001)
186-186: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
195-195: Do not catch blind exception: Exception
(BLE001)
196-196: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
232-232: Do not catch blind exception: Exception
(BLE001)
233-233: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
234-236: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
235-235: Use explicit conversion flag
Replace with conversion flag
(RUF010)
241-241: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
304-304: Do not catch blind exception: Exception
(BLE001)
305-305: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
306-308: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
307-307: Use explicit conversion flag
Replace with conversion flag
(RUF010)
343-343: Do not catch blind exception: Exception
(BLE001)
344-344: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
345-347: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
346-346: Use explicit conversion flag
Replace with conversion flag
(RUF010)
352-352: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
365-367: Abstract raise to an inner function
(TRY301)
370-370: Undefined name uuid
(F821)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
461-461: Do not catch blind exception: Exception
(BLE001)
462-462: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
463-465: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
464-464: Use explicit conversion flag
Replace with conversion flag
(RUF010)
490-490: Possible binding to all interfaces
(S104)
src/models/emotion_detection/samo_bert_emotion_classifier.py
1-1: Shebang is present but file is not executable
(EXE001)
204-204: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
349-352: Avoid specifying long messages outside the exception class
(TRY003)
407-409: Avoid specifying long messages outside the exception class
(TRY003)
scripts/start_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
26-26: Possible binding to all interfaces
(S104)
67-67: Undefined name logger
(F821)
68-68: Do not catch blind exception: Exception
(BLE001)
69-69: Undefined name logger
(F821)
70-70: Undefined name logger
(F821)
110-110: Do not catch blind exception: Exception
(BLE001)
111-111: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
test_samo_emotion_detection_standalone.py
1-1: Shebang is present but file is not executable
(EXE001)
100-100: Use explicit conversion flag
Replace with conversion flag
(RUF010)
105-105: Do not catch blind exception: Exception
(BLE001)
224-224: Do not catch blind exception: Exception
(BLE001)
276-276: Do not catch blind exception: Exception
(BLE001)
tests/test_unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
🔇 Additional comments (5)
scripts/testing/cloud_run_deployment_monitor.py (1)
166-174: Pass service URL via env (now fixed by adding os import).Looks good after the import fix.
src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
122-128: Nice: device selection honors config override.Good improvement; respects user-specified device and falls back to CUDA/CPU.
tests/test_unified_api_server.py (2)
21-25: LGTM! Clean fixture implementationGood use of pytest fixtures to create the API server instance for testing.
129-164: Excellent edge-case test coverageGreat job adding comprehensive tests for ambiguous text, multi-emotion text, and mixed emotions. This addresses the previous review comment about missing edge-case tests.
deployment/cloud-run/secure_api_server.py (1)
131-135: DeBERTa configuration looks goodThe environment variables for DeBERTa are properly configured at startup, ensuring the model loads correctly.
| if USE_DEBERTA: | ||
| class CustomPipeline(TextClassificationPipeline): | ||
| def __init__(self, model, tokenizer, **kwargs): | ||
| super().__init__(model=model, tokenizer=tokenizer, **kwargs) | ||
|
|
||
| def __call__(self, inputs, **kwargs): | ||
| # Override to handle DeBERTa tokenizer issues | ||
| if isinstance(inputs, str): | ||
| inputs = [inputs] | ||
|
|
||
| results = [] | ||
| for text in inputs: | ||
| # Manual tokenization and inference | ||
| encoded = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256) | ||
| with torch.no_grad(): | ||
| outputs = self.model(**encoded) | ||
| predictions = torch.sigmoid(outputs.logits).squeeze(0) | ||
|
|
||
| # Convert to expected format | ||
| emotions = [] | ||
| emotion_labels = [ | ||
| 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', | ||
| 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', | ||
| 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', | ||
| 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', | ||
| 'relief', 'remorse', 'sadness', 'surprise', 'neutral' | ||
| ] | ||
|
|
||
| for i, score in enumerate(predictions): | ||
| if score > 0.05: # Only include significant emotions | ||
| emotions.append({ | ||
| 'label': emotion_labels[i] if i < len(emotion_labels) else f'LABEL_{i}', | ||
| 'score': float(score) | ||
| }) | ||
|
|
||
| emotions.sort(key=lambda x: x['score'], reverse=True) | ||
| results.append(emotions) | ||
|
|
||
| return results | ||
|
|
||
| return CustomPipeline(model=model, tokenizer=tokenizer, return_all_scores=True) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
DeBERTa CustomPipeline implementation needs improvements
The CustomPipeline has several issues that need attention:
- Line 75: The
kwargsparameter is unused in the__call__method - Hard-coded emotion labels should be sourced from a central location
- Missing error handling for tokenizer/model failures
- def __call__(self, inputs, **kwargs):
+ def __call__(self, inputs):
# Override to handle DeBERTa tokenizer issues
if isinstance(inputs, str):
inputs = [inputs]
results = []
for text in inputs:
- # Manual tokenization and inference
- encoded = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256)
- with torch.no_grad():
- outputs = self.model(**encoded)
- predictions = torch.sigmoid(outputs.logits).squeeze(0)
-
- # Convert to expected format
- emotions = []
- emotion_labels = [
- 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring',
- 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval',
- 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief',
- 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization',
- 'relief', 'remorse', 'sadness', 'surprise', 'neutral'
- ]
-
- for i, score in enumerate(predictions):
- if score > 0.05: # Only include significant emotions
- emotions.append({
- 'label': emotion_labels[i] if i < len(emotion_labels) else f'LABEL_{i}',
- 'score': float(score)
- })
-
- emotions.sort(key=lambda x: x['score'], reverse=True)
- results.append(emotions)
+ try:
+ # Manual tokenization and inference
+ encoded = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256)
+ with torch.no_grad():
+ outputs = self.model(**encoded)
+ predictions = torch.sigmoid(outputs.logits).squeeze(0)
+
+ # Convert to expected format
+ emotions = []
+ # Use the same emotion labels as in _predict_emotions_deberta
+ emotion_labels = emotion_labels_runtime if USE_DEBERTA else EMOTION_LABELS
+
+ for i, score in enumerate(predictions):
+ if score > 0.05: # Only include significant emotions
+ emotions.append({
+ 'label': emotion_labels[i] if i < len(emotion_labels) else f'LABEL_{i}',
+ 'score': float(score)
+ })
+
+ emotions.sort(key=lambda x: x['score'], reverse=True)
+ results.append(emotions)
+ except Exception as e:
+ logger.error(f"Failed to process text in CustomPipeline: {e}")
+ results.append([])
return resultsCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.12.2)
75-75: Unused method argument: kwargs
(ARG002)
🤖 Prompt for AI Agents
In deployment/cloud-run/model_utils.py around lines 70 to 110, the
CustomPipeline __call__ ignores kwargs, hard-codes emotion labels, and lacks
error handling; fix by using kwargs when calling the tokenizer and model (e.g.,
self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256,
**kwargs) and self.model(**encoded, **kwargs)), source labels from the
model/config or a central constant (e.g., try model.config.id2label or import a
shared EMOTION_LABELS list) instead of the inline list, and wrap
tokenization/inference in a try/except to catch and log/raise tokenizer or model
errors with clear context; also ensure returned result format stays consistent
with return_all_scores by preserving label/score structure and that kwargs
passed through from the pipeline init are honored.
| emotion_labels = [ | ||
| 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', | ||
| 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', | ||
| 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', | ||
| 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', | ||
| 'relief', 'remorse', 'sadness', 'surprise', 'neutral' | ||
| ] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
DRY violation: Duplicate emotion labels definition
The emotion labels are defined in multiple places (lines 90-96, 155-161, 354-360). This violates the DRY principle and makes maintenance error-prone.
Consider creating a constant at the module level:
+# DeBERTa emotion labels (28 emotions) - single source of truth
+DEBERTA_EMOTION_LABELS = [
+ 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring',
+ 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval',
+ 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief',
+ 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization',
+ 'relief', 'remorse', 'sadness', 'surprise', 'neutral'
+]
def _predict_emotions_deberta(text: str) -> List[Dict[str, Any]]:
"""Direct emotion prediction for DeBERTa model bypassing pipeline.
...
"""
# ... existing code ...
- # DeBERTa emotion labels (28 emotions)
- emotion_labels = [
- 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring',
- 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval',
- 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief',
- 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization',
- 'relief', 'remorse', 'sadness', 'surprise', 'neutral'
- ]
+ emotion_labels = DEBERTA_EMOTION_LABELS📝 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.
| emotion_labels = [ | |
| 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', | |
| 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', | |
| 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', | |
| 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', | |
| 'relief', 'remorse', 'sadness', 'surprise', 'neutral' | |
| ] | |
| # DeBERTa emotion labels (28 emotions) - single source of truth | |
| DEBERTA_EMOTION_LABELS = [ | |
| 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', | |
| 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', | |
| 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', | |
| 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', | |
| 'relief', 'remorse', 'sadness', 'surprise', 'neutral' | |
| ] | |
| def _predict_emotions_deberta(text: str) -> List[Dict[str, Any]]: | |
| """Direct emotion prediction for DeBERTa model bypassing pipeline. | |
| ... | |
| """ | |
| # ... existing code ... | |
| emotion_labels = DEBERTA_EMOTION_LABELS |
🤖 Prompt for AI Agents
In deployment/cloud-run/model_utils.py around lines 155-161 (and duplicated at
~90-96 and ~354-360), the emotion labels list is duplicated; extract the list to
a single module-level constant (e.g., EMOTION_LABELS) defined near the top of
the file and replace the three inline lists with references to that constant;
ensure any code that mutates the list makes a copy (list(EMOTION_LABELS)) to
avoid accidental modification, and run tests/lint to confirm no import or name
collisions.
|
|
||
| # Force download the model | ||
| from huggingface_hub import snapshot_download | ||
| download_dir = f"/tmp/{model_name.replace('/', '_')}" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Security: Hardcoded /tmp directory usage
Using hardcoded /tmp paths can be insecure. Consider using Python's tempfile module for temporary directories.
+import tempfile
+
# In the download section:
- download_dir = f"/tmp/{model_name.replace('/', '_')}"
+ download_dir = os.path.join(tempfile.gettempdir(), model_name.replace('/', '_'))Also applies to: 332-332
🧰 Tools
🪛 Ruff (0.12.2)
293-293: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
🤖 Prompt for AI Agents
In deployment/cloud-run/model_utils.py around lines 293 and 332, the code
constructs temporary paths by hardcoding "/tmp/<model_name...>", which is
insecure and non-portable; replace these with a securely created temporary
directory using Python's tempfile (e.g., tempfile.TemporaryDirectory or
tempfile.mkdtemp) and join model-specific filenames into that directory, ensure
proper cleanup (context manager or try/finally) and avoid using literal "/tmp"
so temp dirs are OS-appropriate and race-condition safe.
| # Reset global state | ||
| model_utils.emotion_pipeline = None | ||
| model_utils.model_loaded = False | ||
| model_utils.model_loading = False | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Reset tokenizer/model too to avoid cross-test leakage.
Without clearing these, re-loads can be skipped or mixed.
# Reset global state
model_utils.emotion_pipeline = None
+ model_utils.emotion_tokenizer = None
+ model_utils.emotion_model = None
model_utils.model_loaded = False
model_utils.model_loading = FalseApply in both test functions.
📝 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.
| # Reset global state | |
| model_utils.emotion_pipeline = None | |
| model_utils.model_loaded = False | |
| model_utils.model_loading = False | |
| # Reset global state | |
| model_utils.emotion_pipeline = None | |
| model_utils.emotion_tokenizer = None | |
| model_utils.emotion_model = None | |
| model_utils.model_loaded = False | |
| model_utils.model_loading = False |
🤖 Prompt for AI Agents
In scripts/testing/test_model_switching.py around lines 32 to 36, the test
teardown resets only emotion_pipeline and loading flags which can leave
tokenizer/model state leaking between tests; update the cleanup to also set
model_utils.model = None and model_utils.tokenizer = None (or the actual
attribute names used for the loaded model and tokenizer) and apply the same
reset in both test functions so each test starts with a clean model/tokenizer
state.
| # Test prediction | ||
| test_text = "I am so happy today!" | ||
| try: | ||
| result = model_utils.predict_emotions(test_text) | ||
| top_emotion = result[0]['emotion'] if result else 'unknown' | ||
| confidence = result[0]['confidence'] if result else 0.0 | ||
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | ||
| print(f"📊 Emotions detected: {len(result) if result else 0}") | ||
| except Exception as e: |
There was a problem hiding this comment.
Fix result handling: predict_emotions returns a dict with an 'emotions' list, not a list.
Current indexing treats the whole result as a list and will throw/print wrong values.
Apply:
- result = model_utils.predict_emotions(test_text)
- top_emotion = result[0]['emotion'] if result else 'unknown'
- confidence = result[0]['confidence'] if result else 0.0
- print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})")
- print(f"📊 Emotions detected: {len(result) if result else 0}")
+ result = model_utils.predict_emotions(test_text)
+ emotions = result.get('emotions', [])
+ top_emotion = emotions[0]['emotion'] if emotions else 'unknown'
+ confidence = emotions[0]['confidence'] if emotions else 0.0
+ print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})")
+ print(f"📊 Emotions detected: {len(emotions)}")📝 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.
| # Test prediction | |
| test_text = "I am so happy today!" | |
| try: | |
| result = model_utils.predict_emotions(test_text) | |
| top_emotion = result[0]['emotion'] if result else 'unknown' | |
| confidence = result[0]['confidence'] if result else 0.0 | |
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | |
| print(f"📊 Emotions detected: {len(result) if result else 0}") | |
| except Exception as e: | |
| # Test prediction | |
| test_text = "I am so happy today!" | |
| try: | |
| result = model_utils.predict_emotions(test_text) | |
| emotions = result.get('emotions', []) | |
| top_emotion = emotions[0]['emotion'] if emotions else 'unknown' | |
| confidence = emotions[0]['confidence'] if emotions else 0.0 | |
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | |
| print(f"📊 Emotions detected: {len(emotions)}") | |
| except Exception as e: |
🧰 Tools
🪛 Ruff (0.12.2)
49-49: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In scripts/testing/test_model_switching.py around lines 41 to 49, the current
code treats the return value from model_utils.predict_emotions as a list and
indexes result[0], but predict_emotions returns a dict with an 'emotions' list;
update the handling to extract emotions = result.get('emotions', []) (or [] if
result is falsy), then compute top_emotion = emotions[0]['emotion'] if emotions
else 'unknown', confidence = emotions[0]['confidence'] if emotions else 0.0, and
use len(emotions) for the count; keep the try/except but change the prints to
reference these corrected variables.
| # Reset global state | ||
| model_utils.emotion_pipeline = None | ||
| model_utils.model_loaded = False | ||
| model_utils.model_loading = False | ||
|
|
||
| success = model_utils.ensure_model_loaded() | ||
| print(f"✅ DeBERTa model loaded: {success}") | ||
|
|
||
| if success: | ||
| # Test prediction | ||
| test_text = "I am so happy today!" | ||
| try: | ||
| result = model_utils.predict_emotions(test_text) | ||
| top_emotion = result[0]['emotion'] if result else 'unknown' | ||
| confidence = result[0]['confidence'] if result else 0.0 | ||
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | ||
| print(f"📊 Emotions detected: {len(result) if result else 0}") | ||
| except Exception as e: |
There was a problem hiding this comment.
Same bug in DeBERTa path.
Mirror the fix for the DeBERTa test block.
- result = model_utils.predict_emotions(test_text)
- top_emotion = result[0]['emotion'] if result else 'unknown'
- confidence = result[0]['confidence'] if result else 0.0
- print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})")
- print(f"📊 Emotions detected: {len(result) if result else 0}")
+ result = model_utils.predict_emotions(test_text)
+ emotions = result.get('emotions', [])
+ top_emotion = emotions[0]['emotion'] if emotions else 'unknown'
+ confidence = emotions[0]['confidence'] if emotions else 0.0
+ print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})")
+ print(f"📊 Emotions detected: {len(emotions)}")📝 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.
| # Reset global state | |
| model_utils.emotion_pipeline = None | |
| model_utils.model_loaded = False | |
| model_utils.model_loading = False | |
| success = model_utils.ensure_model_loaded() | |
| print(f"✅ DeBERTa model loaded: {success}") | |
| if success: | |
| # Test prediction | |
| test_text = "I am so happy today!" | |
| try: | |
| result = model_utils.predict_emotions(test_text) | |
| top_emotion = result[0]['emotion'] if result else 'unknown' | |
| confidence = result[0]['confidence'] if result else 0.0 | |
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | |
| print(f"📊 Emotions detected: {len(result) if result else 0}") | |
| except Exception as e: | |
| # Reset global state | |
| model_utils.emotion_pipeline = None | |
| model_utils.model_loaded = False | |
| model_utils.model_loading = False | |
| success = model_utils.ensure_model_loaded() | |
| print(f"✅ DeBERTa model loaded: {success}") | |
| if success: | |
| # Test prediction | |
| test_text = "I am so happy today!" | |
| try: | |
| result = model_utils.predict_emotions(test_text) | |
| emotions = result.get('emotions', []) | |
| top_emotion = emotions[0]['emotion'] if emotions else 'unknown' | |
| confidence = emotions[0]['confidence'] if emotions else 0.0 | |
| print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") | |
| print(f"📊 Emotions detected: {len(emotions)}") | |
| except Exception as e: |
🧰 Tools
🪛 Ruff (0.12.2)
84-84: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In scripts/testing/test_model_switching.py around lines 67 to 84, the DeBERTa
test block's try/except is incomplete and doesn't mirror the fix applied to the
other model path; update this block to catch Exception as e, print a clear error
message including the exception (and optionally traceback), set success to False
or handle the failure case similarly to the other path, and ensure downstream
code safely handles a None or empty result (use conditional checks before
indexing result).
| class WeightedBCELoss(nn.Module): | ||
| """Weighted Binary Cross Entropy Loss for multi-label emotion classification.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| class_weights: Optional[torch.Tensor] = None, | ||
| reduction: str = "mean", | ||
| ) -> None: | ||
| """ | ||
| Initialize weighted BCE loss. | ||
|
|
||
| Args: | ||
| class_weights: Class weights for balancing loss | ||
| reduction: Loss reduction method | ||
| """ | ||
| super().__init__() | ||
| self.class_weights = class_weights | ||
| self.reduction = reduction | ||
|
|
||
| def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor: | ||
| """ | ||
| Compute weighted BCE loss. | ||
|
|
||
| Args: | ||
| logits: Model predictions | ||
| targets: Ground truth labels | ||
|
|
||
| Returns: | ||
| Weighted BCE loss | ||
| """ | ||
| # Apply sigmoid to get probabilities | ||
| probabilities = torch.sigmoid(logits) | ||
|
|
||
| # Compute BCE loss | ||
| bce_loss = F.binary_cross_entropy( | ||
| probabilities, targets.float(), reduction="none" | ||
| ) | ||
|
|
||
| # Apply class weights if provided | ||
| if self.class_weights is not None: | ||
| # Ensure class weights are properly shaped for broadcasting | ||
| if self.class_weights.shape[0] != bce_loss.shape[1]: | ||
| raise ValueError( | ||
| f"Class weights shape {self.class_weights.shape} does not match " | ||
| f"number of classes {bce_loss.shape[1]}" | ||
| ) | ||
| bce_loss = bce_loss * self.class_weights.unsqueeze(0) | ||
|
|
||
| # Apply reduction | ||
| if self.reduction == "mean": | ||
| return bce_loss.mean() | ||
| if self.reduction == "sum": | ||
| return bce_loss.sum() | ||
| return bce_loss |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Numerical stability: use BCEWithLogits for loss.
Compute BCE directly on logits to avoid sigmoid + BCE instability; apply class weights via the weight arg.
- # Apply sigmoid to get probabilities
- probabilities = torch.sigmoid(logits)
-
- # Compute BCE loss
- bce_loss = F.binary_cross_entropy(
- probabilities, targets.float(), reduction="none"
- )
-
- # Apply class weights if provided
- if self.class_weights is not None:
- # Ensure class weights are properly shaped for broadcasting
- if self.class_weights.shape[0] != bce_loss.shape[1]:
- raise ValueError(
- f"Class weights shape {self.class_weights.shape} does not match "
- f"number of classes {bce_loss.shape[1]}"
- )
- bce_loss = bce_loss * self.class_weights.unsqueeze(0)
+ # Compute BCE loss on logits for stability
+ weight = None
+ if self.class_weights is not None:
+ if self.class_weights.shape[-1] != logits.shape[-1]:
+ raise ValueError(
+ f"Class weights shape {self.class_weights.shape} does not match number of classes {logits.shape[-1]}"
+ )
+ weight = self.class_weights
+ bce_loss = F.binary_cross_entropy_with_logits(
+ logits, targets.float(), weight=weight, reduction="none"
+ )📝 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 WeightedBCELoss(nn.Module): | |
| """Weighted Binary Cross Entropy Loss for multi-label emotion classification.""" | |
| def __init__( | |
| self, | |
| class_weights: Optional[torch.Tensor] = None, | |
| reduction: str = "mean", | |
| ) -> None: | |
| """ | |
| Initialize weighted BCE loss. | |
| Args: | |
| class_weights: Class weights for balancing loss | |
| reduction: Loss reduction method | |
| """ | |
| super().__init__() | |
| self.class_weights = class_weights | |
| self.reduction = reduction | |
| def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Compute weighted BCE loss. | |
| Args: | |
| logits: Model predictions | |
| targets: Ground truth labels | |
| Returns: | |
| Weighted BCE loss | |
| """ | |
| # Apply sigmoid to get probabilities | |
| probabilities = torch.sigmoid(logits) | |
| # Compute BCE loss | |
| bce_loss = F.binary_cross_entropy( | |
| probabilities, targets.float(), reduction="none" | |
| ) | |
| # Apply class weights if provided | |
| if self.class_weights is not None: | |
| # Ensure class weights are properly shaped for broadcasting | |
| if self.class_weights.shape[0] != bce_loss.shape[1]: | |
| raise ValueError( | |
| f"Class weights shape {self.class_weights.shape} does not match " | |
| f"number of classes {bce_loss.shape[1]}" | |
| ) | |
| bce_loss = bce_loss * self.class_weights.unsqueeze(0) | |
| # Apply reduction | |
| if self.reduction == "mean": | |
| return bce_loss.mean() | |
| if self.reduction == "sum": | |
| return bce_loss.sum() | |
| return bce_loss | |
| class WeightedBCELoss(nn.Module): | |
| """Weighted Binary Cross Entropy Loss for multi-label emotion classification.""" | |
| def __init__( | |
| self, | |
| class_weights: Optional[torch.Tensor] = None, | |
| reduction: str = "mean", | |
| ) -> None: | |
| """ | |
| Initialize weighted BCE loss. | |
| Args: | |
| class_weights: Class weights for balancing loss | |
| reduction: Loss reduction method | |
| """ | |
| super().__init__() | |
| self.class_weights = class_weights | |
| self.reduction = reduction | |
| def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Compute weighted BCE loss. | |
| Args: | |
| logits: Model predictions | |
| targets: Ground truth labels | |
| Returns: | |
| Weighted BCE loss | |
| """ | |
| # Compute BCE loss on logits for stability | |
| weight = None | |
| if self.class_weights is not None: | |
| if self.class_weights.shape[-1] != logits.shape[-1]: | |
| raise ValueError( | |
| f"Class weights shape {self.class_weights.shape} does not match number of classes {logits.shape[-1]}" | |
| ) | |
| weight = self.class_weights | |
| bce_loss = F.binary_cross_entropy_with_logits( | |
| logits, targets.float(), weight=weight, reduction="none" | |
| ) | |
| # Apply reduction | |
| if self.reduction == "mean": | |
| return bce_loss.mean() | |
| if self.reduction == "sum": | |
| return bce_loss.sum() | |
| return bce_loss |
🧰 Tools
🪛 Ruff (0.12.2)
349-352: Avoid specifying long messages outside the exception class
(TRY003)
- Add num_emotions parameter to EmotionDataset.__init__ method - Assign self.num_emotions = num_emotions to fix AttributeError in __getitem__ - Update docstring to document the new parameter - Fixes runtime error when EmotionDataset is used for validation - Default value is 28 emotions to match SAMO model configuration
- Extract model pre-downloading logic into dedicated scripts/download_model.py - Fix fragile sys.path manipulation in start_api_server.py with proper imports - Optimize gunicorn configuration: 5 workers + 2 threads for 2-CPU instance - Add comprehensive error handling and documentation - Improve maintainability and follow Docker/Python best practices - Expected 5x improvement in concurrent request handling
Resolved issues in the following files with DeepSource Autofix: 1. deployment/cloud-run/model_utils.py 2. scripts/testing/cloud_run_deployment_monitor.py 3. tests/test_unified_api_server.py
Resolved issues in the following files with DeepSource Autofix: 1. scripts/download_model.py 2. test_samo_emotion_detection_standalone.py
There was a problem hiding this comment.
Actionable comments posted: 17
♻️ Duplicate comments (31)
scripts/testing/scientific_cloud_run_testing.py (5)
197-200: Fix malformed status print (syntax error).Replace the broken print with a valid f-string.
- status = "✅" if result.success else "❌" - print(".2f" + status = "✅" if result.success else "❌" + print(f" {status} {entry['id']}: {result.latency:.2f}s") print()
222-224: Repair broken multi-line print in load test header.Two separate, valid print calls.
- print(" -🔥 LOAD TESTING - High Concurrency" print(f"⚡ Concurrent Requests: {concurrent_requests}") + print("\n🔥 LOAD TESTING - High Concurrency") + print(f"⚡ Concurrent Requests: {concurrent_requests}") print("-" * 40)
241-244: Fix malformed per-request latency print in load test.Use the completed TestResult and format latency.
- status = "✅" if result.success else "❌" - print(".2f" + status = "✅" if result.success else "❌" + print(f" {status} Request {result.run_id}: {result.latency:.2f}s") total_time = time.time() - start_time
265-268: Repair broken multi-line print in reliability test header.- print(" -🔄 RELIABILITY TESTING" print(f"📊 Iterations: {num_iterations}") + print("\n🔄 RELIABILITY TESTING") + print(f"📊 Iterations: {num_iterations}") print("-" * 40)
360-370: Fix statistical report prints (multiple syntax errors).Replace placeholders with concrete f-strings referencing analysis fields.
print("🎯 SUCCESS METRICS:") - print(".1f" print(".2f" + print(f" Success Rate: {analysis.success_rate * 100:.1f}%") + print(f" Throughput: {analysis.throughput:.2f} req/s") print() print("⚡ LATENCY ANALYSIS:") - print(".2f" print(".2f" print(".2f" print(".2f" print(".2f" print(".2f" print() + print(f" Mean: {analysis.mean_latency:.2f}s") + print(f" Median: {analysis.median_latency:.2f}s") + print(f" Std Dev: {analysis.std_dev_latency:.2f}s") + print(f" Min: {analysis.min_latency:.2f}s") + print(f" Max: {analysis.max_latency:.2f}s") + print(f" P95: {analysis.p95_latency:.2f}s") + print(f" P99: {analysis.p99_latency:.2f}s") + print() print("📈 PERFORMANCE METRICS:") - print(".2f" print(".2f" + print(f" Requests/Second: {analysis.throughput:.2f}") + print(f" Mean Response Time: {analysis.mean_latency:.2f}s") print() print("🔬 STATISTICAL CONFIDENCE:") - print(".2f" print(".2f" + print(f" Confidence Level: {self.config.confidence_level * 100:.1f}%") + print(f" CI Lower: {analysis.confidence_interval[0]:.2f}s") + print(f" CI Upper: {analysis.confidence_interval[1]:.2f}s") print()scripts/testing/cloud_run_deployment_monitor.py (1)
9-15: Import missing os module.Used below for environment handling.
import sys +import os import time import requests import subprocess from pathlib import Path from typing import Optional, Dict, Anyscripts/testing/test_deberta_api.py (3)
88-99: Use correct admin endpoint path.- response = requests.get(f"{API_BASE_URL}/admin/model_status", headers=HEADERS, timeout=10) + response = requests.get(f"{API_BASE_URL}/admin/model/status", headers=HEADERS, timeout=10)
128-144: Fix batch endpoint path and response handling.- response = requests.post( - f"{API_BASE_URL}/api/predict_batch", + response = requests.post( + f"{API_BASE_URL}/api/predict/batch", json=test_payload, headers=HEADERS, timeout=API_TIMEOUT ) @@ - if response.status_code == 200: - results = response.json() + if response.status_code == 200: + payload = response.json() + results = payload.get("results", []) logger.info("✅ Batch emotion prediction successful") @@ - for i, result in enumerate(results): - if 'emotions' in result and result['emotions']: - top_emotion = result['emotions'][0] + for i, result in enumerate(results): + emotions = result.get('emotions') or [] + if emotions: + top_emotion = emotions[0] logger.info(f"📝 Text {i+1}: {top_emotion['emotion']}:{top_emotion['confidence']:.3f}")
63-67: Fix f-string quoting causing SyntaxError; simplify join.- if 'emotions' in result and result['emotions']: - top_emotions = result['emotions'][:3] - logger.info(f"📝 Prediction result: {', '.join([f'{e['emotion']}:{e['confidence']:.3f}' for e in top_emotions])}") + emotions = result.get('emotions') or [] + if emotions: + top_emotions = emotions[:3] + logger.info("📝 Prediction result: " + ", ".join(f"{e['emotion']}:{e['confidence']:.3f}" for e in top_emotions))scripts/testing/quick_deployment_check.py (2)
42-49: Health check should call /api/health, not service root.- response = requests.get(service_url, timeout=5) + response = requests.get(f"{service_url.rstrip('/')}/api/health", timeout=5)
57-66: Fix endpoint path and syntax error in print; validate emotions list.- api_url = f"{service_url}/analyze" + api_url = f"{service_url.rstrip('/')}/api/predict" payload = {"text": "I feel happy!"} response = requests.post(api_url, json=payload, timeout=10) if response.status_code == 200: data = response.json() - if "primary_emotion" in data: - emotion = data.get("primary_emotion", "unknown") - print("🎯 API working!" print(f" Sample result: {emotion}") + emotions = data.get("emotions") or [] + if emotions: + emotion = emotions[0].get("emotion", "unknown") + print("🎯 API working!") + print(f" Sample result: {emotion}") print() print("🚀 DEPLOYMENT COMPLETE AND READY FOR TESTING!") print(f"🌐 Service URL: {service_url}") return service_url else: print("⚠️ API responding but unexpected format") return Falsescripts/testing/debug_deberta_loading.py (3)
159-162: Use a safe temp cache dir instead of /tmp literal+import tempfile @@ - cache_dir = "/tmp/deberta_cache" + cache_dir = os.path.join(tempfile.gettempdir(), "deberta_cache")Also applies to: 13-17
54-55: Replace placeholder prints with real formatted metrics- print(".2f") + print(f"✅ Loaded in {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference: {inference_time:.3f}s") @@ - print(".2f") + print(f"✅ Loaded in {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference: {inference_time:.3f}s") @@ - print(".2f") + print(f"📥 Downloaded in {download_time:.2f}s") @@ - print(".2f") + print(f"✅ Loaded in {load_time:.2f}s") @@ - print(".3f") + print(f"⏱️ Inference: {inference_time:.3f}s")Also applies to: 62-63, 119-121, 137-141, 172-173, 189-190, 197-198
235-246: Fix double-escaped newlines- print("\\n📊 Testing DeBERTa Model") + print("\n📊 Testing DeBERTa Model") @@ - print(f"\\n🔄 Trying Method {i}...") + print(f"\n🔄 Trying Method {i}...") @@ - print("\\n✅ SUCCESS: DeBERTa model working!") + print("\n✅ SUCCESS: DeBERTa model working!") @@ - print("\\n❌ All DeBERTa loading methods failed") + print("\n❌ All DeBERTa loading methods failed") @@ - print("\\n" + "=" * 50) + print("\n" + "=" * 50)Also applies to: 249-255, 274-275
scripts/testing/deberta_journal_inference_demo.py (1)
101-109: Fix id2label lookup: keys are typically ints, not strings- if str(i) in self.model.config.id2label: - emotion_name = self.model.config.id2label[str(i)] + if hasattr(self.model.config, "id2label") and i in self.model.config.id2label: + emotion_name = self.model.config.id2label[i]scripts/testing/model_comparison_test.py (3)
249-249: Type hints: Optional parameters should be Optional[...]-from typing import Dict, List, Any, Optional +from typing import Dict, List, Any, Optional @@ - def run_comprehensive_benchmark(self, test_texts: List[str] = None, test_data: List[Dict[str, Any]] = None) -> Dict[str, Any]: + def run_comprehensive_benchmark(self, test_texts: Optional[List[str]] = None, test_data: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:Also applies to: 21-22
270-276: results['models_loaded'] is captured before loading; move after loads- results = { - 'timestamp': datetime.now().isoformat(), - 'device': str(self.device), - 'models_loaded': list(self.models.keys()), - 'inference_benchmarks': {}, - 'accuracy_benchmarks': {} - } - - # Load all models + # Load all models self.load_current_bert_model() self.load_deberta_model() self.load_production_model() + + results = { + 'timestamp': datetime.now().isoformat(), + 'device': str(self.device), + 'models_loaded': list(self.models.keys()), + 'inference_benchmarks': {}, + 'accuracy_benchmarks': {} + }Also applies to: 278-282
329-333: Replace placeholder prints with formatted metrics in summary- print(f"📈 {model_key.upper()}:") - print(".2f") - print(".2f") - print(".1f") + print(f"📈 {model_key.upper()}:") + print(f" avg_latency_ms: {data.get('avg_latency_ms', 0):.2f}") + print(f" std_latency_ms: {data.get('std_latency_ms', 0):.2f}") + print(f" throughput_texts_per_sec: {data.get('throughput_texts_per_sec', 0):.1f}") @@ - print(f"🎯 {model_key.upper()}:") - print(".1f") - print(".2f") + print(f"🎯 {model_key.upper()}:") + print(f" accuracy: {data.get('accuracy', 0):.1%}") + print(f" avg_latency_ms: {data.get('avg_latency_ms', 0):.2f}")Also applies to: 344-347
scripts/testing/comprehensive_journal_inference_demo.py (2)
47-51: Label set mismatch (12 vs model’s 28 GoEmotions) — load dynamically or use 28Hardcoding 12 labels will misalign indices and corrupt results when the model outputs 28 logits.
- # SAMO emotion labels (12 emotions for the current model) - self.emotion_labels = [ - "anxious", "calm", "content", "excited", "frustrated", - "grateful", "happy", "hopeful", "overwhelmed", "proud", "sad", "tired" - ] + # Load labels dynamically from config if available; fallback to 28-class GoEmotions + self.emotion_labels = self._load_emotion_labels() + + def _load_emotion_labels(self): + config_path = self.model_path / "config.json" + try: + if config_path.exists(): + import json as _json + with open(config_path, "r", encoding="utf-8") as f: + cfg = _json.load(f) + id2label = cfg.get("id2label") or {} + # keys may be str + labels = [id2label.get(str(i), id2label.get(i)) for i in range(len(id2label))] + if all(isinstance(x, str) for x in labels): + return labels + except Exception: + pass + return [ + "admiration","amusement","anger","annoyance","approval","caring", + "confusion","curiosity","desire","disappointment","disapproval","disgust", + "embarrassment","excitement","fear","gratitude","grief","joy","love", + "nervousness","optimism","pride","realization","relief","remorse", + "sadness","surprise","neutral" + ]
106-110: Bounds check when indexing labels to avoid IndexError- if pred: # Only include emotions above threshold - predicted_emotions.append(self.emotion_labels[i]) - emotion_scores.append(float(prob)) + if pred: + if i < len(self.emotion_labels): + predicted_emotions.append(self.emotion_labels[i]) + emotion_scores.append(float(prob))scripts/start_api_server.py (2)
16-16: Prefer packaging over mutatingsys.pathInstall the project (e.g.,
pip install -e .) and import from the package instead of insertingsrcintosys.path.
88-99: Apply CORS env before server creation
SAMOUnifiedAPIServerreadsAPI_ALLOWED_ORIGINSat init; setting it after instantiation is too late.Apply:
@@ - # Create and start server with configuration - server = SAMOUnifiedAPIServer() - - # Apply configuration if available - if config: - # Apply server configuration - if 'server' in config: - server_config = config['server'] - if 'cors_origins' in server_config: - os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) - logger.info("✅ Applied server configuration") + # Apply configuration before creating server + if config and 'server' in config: + server_config = config['server'] + if 'cors_origins' in server_config: + os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) + logger.info("✅ Applied server configuration") + + # Create and start server with configuration + server = SAMOUnifiedAPIServer()scripts/testing/test_deberta_isolated.py (1)
63-66: Fix f-string quoting (syntax error on Python <3.12)Nested single quotes inside the f-string break parsing.
Apply:
- # Log top 3 emotions - top_emotions = result['emotions'][:3] - logger.info(f"📝 '{text}' -> {', '.join([f'{e['emotion']}:{e['confidence']:.3f}' for e in top_emotions])}") + # Log top 3 emotions + top_emotions = result['emotions'][:3] + parts = [f"{e['emotion']}:{e['confidence']:.3f}" for e in top_emotions] + logger.info("📝 '%s' -> %s", text, ", ".join(parts))scripts/testing/quick_model_test.py (1)
50-53: Fix malformed print statements (missing interpolations)These currently print literal format specifiers and hide values.
Apply:
print("✅ Inference completed successfully!") - print(".2f") + print(f"⏱️ Inference time: {inference_time:.2f}s") print(f"📊 Results structure: {list(results.keys())}") @@ print("✅ Inference completed successfully!") - print(".2f") + print(f"⏱️ Inference time: {inference_time:.2f}s") @@ - print(f"Text: {text}") - print(".3f") + print(f"Text: {text}") + print(f" Emotion: {emotion} (confidence: {confidence:.3f})") @@ print("✅ Inference completed successfully!") - print(".2f") + print(f"⏱️ Inference time: {inference_time:.2f}s") @@ - print(f"Text: {text}") - print(".3f") + print(f"Text: {text}") + print(f" Emotion: {emotion} (confidence: {confidence:.3f})")Also applies to: 103-105, 110-114, 188-190, 196-199
scripts/testing/test_model_switching.py (1)
45-49: Fix result access:predict_emotionsreturns a dict, not a listIndexing
result[0]raises a TypeError.Apply:
- result = model_utils.predict_emotions(test_text) - top_emotion = result[0]['emotion'] if result else 'unknown' - confidence = result[0]['confidence'] if result else 0.0 + result = model_utils.predict_emotions(test_text) + if 'error' in result: + print(f"❌ Prediction failed: {result['error']}") + return False + emotions = result.get('emotions', []) + top_emotion = emotions[0]['emotion'] if emotions else 'unknown' + confidence = emotions[0]['confidence'] if emotions else 0.0 print(f"🎯 Prediction: {top_emotion} ({confidence:.3f})") - print(f"📊 Emotions detected: {len(result) if result else 0}") + print(f"📊 Emotions detected: {len(emotions)}")Also applies to: 80-84
tests/test_unified_api_server.py (1)
21-26: Patch models in the fixture to avoid heavy downloads and nondeterminismInstantiate the server with mocked factories so tests don’t pull real models and remain fast/reliable.
Apply:
- @pytest.fixture - def api_server(self): - """Create API server instance for testing.""" - server = SAMOUnifiedAPIServer() - return server + @pytest.fixture + def api_server(self): + """Create API server instance for testing with mocked models.""" + with patch('src.models.unified_api_server.create_t5_summarizer') as mock_sum, \ + patch('src.models.unified_api_server.create_whisper_transcriber') as mock_tr, \ + patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') as mock_emo: + # Summarizer mock + sum_model = Mock() + sum_model.generate_summary.return_value = "Test summary." + sum_model.get_model_info.return_value = {"model_name": "t5-small"} + mock_sum.return_value = sum_model + # Transcriber mock + tr_model = Mock() + tr_model.transcribe.return_value = Mock(text="stub", language="en", confidence=0.9, + duration=1.0, processing_time=0.1, + audio_quality="ok", word_count=1, + speaking_rate=120.0, no_speech_probability=0.0) + mock_tr.return_value = tr_model + # Emotion detector mock + emo_model = Mock() + emo_model.predict_emotions.return_value = { + "emotions": [["joy", "optimism"]], + "probabilities": [[0.9, 0.6]], + "predictions": [[1, 1]] + } + mock_emo.return_value = emo_model + return SAMOUnifiedAPIServer()src/models/unified_api_server.py (3)
147-158: CORS hardening via env var looks good.Switching to env-configured origins resolves the prior “allow all” risk. Consider guarding against accidentally setting "*" with allow_credentials=True (browsers will reject). Example:
allowed_origins_list = [origin.strip() for origin in allowed_origins.split(",")] +if "*" in allowed_origins_list: + # Disallow credentials when wildcard origin is used + allow_credentials = False +else: + allow_credentials = True ... self.app.add_middleware( CORSMiddleware, allow_origins=allowed_origins_list, - allow_credentials=True, + allow_credentials=allow_credentials, allow_methods=["*"], allow_headers=["*"], )
369-373: Fix NameError: uuid not imported in this scope.process-audio uses uuid but doesn’t import it. Import once at module level.
from datetime import datetime +import uuid
349-376: Replicate MIME-type validation and add upload size limits in /process-audio.This endpoint lacks the MIME validation you added to /transcribe and has no size guard. Add both to prevent large-file DoS and invalid formats.
async def process_audio_completely( background_tasks: BackgroundTasks, file: UploadFile = File(...), language: Optional[str] = Form(None), summary_max_length: Optional[int] = Form(128), emotion_threshold: Optional[float] = Form(0.5) ): """Complete pipeline: Audio -> Transcription -> Summary -> Emotion Analysis.""" pipeline_start = time.time() pipeline_steps = [] try: + # Enforce size limit (env-configurable) + import os, magic + max_bytes = int(os.getenv("MAX_UPLOAD_BYTES", str(25 * 1024 * 1024))) # 25MB default + content = await file.read() + if len(content) > max_bytes: + raise HTTPException(status_code=413, detail="File too large") + mime_type = magic.from_buffer(content, mime=True) + supported_mime_types = { + "audio/mpeg","audio/wav","audio/x-wav","audio/x-m4a","audio/mp4", + "audio/ogg","audio/flac","audio/x-flac", + } + if mime_type not in supported_mime_types: + raise HTTPException(status_code=400, detail=f"Unsupported audio format: {mime_type}") + await file.seek(0) # Step 1: Transcribe audio pipeline_steps.append("transcription") if not self.models["transcriber"]: raise HTTPException( status_code=503, detail="Transcription model not available" ) # Generate unique temporary filename unique_suffix = uuid.uuid4().hex extension = file.filename.split('.')[-1] if '.' in file.filename else '' temp_path = f"/tmp/{unique_suffix}.{extension}" if extension else f"/tmp/{unique_suffix}" with open(temp_path, "wb") as buffer: - content = await file.read() buffer.write(content)deployment/cloud-run/model_utils.py (1)
257-259: Use torch.float32 dtype and remove unused max_length.Passing "float32" as string is ignored by HF. Also drop dead max_length locals.
- model = AutoModelForSequenceClassification.from_pretrained( - EMOTION_MODEL_DIR, local_files_only=True, torch_dtype="float32" - ) + model = AutoModelForSequenceClassification.from_pretrained( + EMOTION_MODEL_DIR, local_files_only=True, torch_dtype=torch.float32 + )- model_kwargs = { - "torch_dtype": "float32", + model_kwargs = { + "torch_dtype": torch.float32, "use_safetensors": True, "ignore_mismatched_sizes": True } - max_length = 256 model_type = "DeBERTa (28 emotions)"- model_kwargs = {"torch_dtype": "float32"} - max_length = 512 + model_kwargs = {"torch_dtype": torch.float32} model_type = "Production (6 emotions)"Also applies to: 264-272, 314-317
src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
259-263: Guard top_k to valid range to prevent runtime errors.torch.topk requires k <= num_classes.
- if top_k is not None: - _, top_k_indices = torch.topk(probabilities, top_k, dim=1) - predictions = torch.zeros_like(probabilities) - predictions.scatter_(1, top_k_indices, 1.0) + if top_k is not None: + num_classes = probabilities.size(1) + k = max(1, min(int(top_k), num_classes)) + top_k_indices = torch.topk(probabilities, k, dim=1).indices + predictions = torch.zeros_like(probabilities) + predictions.scatter_(1, top_k_indices, 1.0)
🧹 Nitpick comments (49)
scripts/testing/scientific_cloud_run_testing.py (1)
1-1: Shebang + permissions.Either make the file executable in the repo or drop the shebang to silence EXE001.
#!/usr/bin/env python3Action: chmod +x scripts/testing/scientific_cloud_run_testing.py (or remove the shebang).
scripts/testing/cloud_run_deployment_monitor.py (4)
157-161: Avoid ambiguous × character in console output.Use ASCII 'x' to prevent encoding issues on some terminals.
- print(" 1. ✅ Comprehensive API testing (10 runs × 5 entries)") + print(" 1. ✅ Comprehensive API testing (10 runs x 5 entries)")
1-1: Shebang + permissions.Either mark the file executable or remove the shebang to silence EXE001.
Action: chmod +x scripts/testing/cloud_run_deployment_monitor.py (or drop the shebang).
72-97: Narrow overly broad exceptions.Consider catching specific requests and JSON errors for clearer diagnostics.
- except Exception as e: + except requests.exceptions.RequestException as e: print(f"❌ API test error: {e}") return False
24-48: Harden gcloud invocation and error handling.Print stderr only when present; keep check=True; timeout is good.
- if result.returncode == 0 and result.stdout.strip(): + if result.returncode == 0 and result.stdout and result.stdout.strip(): url = result.stdout.strip() print(f"✅ Service URL found: {url}") return url - print(f"❌ Failed to get service URL: {result.stderr}") + if result.stderr: + print(f"❌ Failed to get service URL: {result.stderr.strip()}") return Nonedeployment/cloud-run/cloudbuild.yaml (1)
3-5: Make builds reproducible and Cloud Run–compatible (pin platform, add SHA tag, push both).Explicitly build for linux/amd64 and tag by commit SHA for traceability and safe rollbacks.
- args: ['build', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta', '-f', 'deployment/cloud-run/Dockerfile.deberta', '.'] + args: ['build', '--platform=linux/amd64', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta', '-t', 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$SHORT_SHA', '-f', 'deployment/cloud-run/Dockerfile.deberta', '.'] @@ -images: - - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta' +images: + - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta' + - 'us-central1-docker.pkg.dev/the-tendril-466607-n8/samo-dl/samo-emotion-api-deberta:$SHORT_SHA'configs/samo_api_config.yaml (2)
46-47: Align upload limit with Cloud Run HTTP body cap (32 MB).Cloud Run’s HTTP request body max is 32 MB; larger values won’t be honored. Consider lowering to 32 to avoid misleading clients.
- max_upload_size: 100 # MB + max_upload_size: 32 # MB (Cloud Run HTTP limit)
86-86: Add a trailing newline.Fixes YAML lint “no new line at end of file”.
scripts/testing/quick_deployment_check.py (1)
12-12: Remove unused import.Path is unused.
-from pathlib import Pathscripts/testing/deberta_simple_test.py (2)
60-67: Print actual top label/score instead of placeholder text.- result = clf(text) - top_emotion = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} - print(f"Text: {text}") - print(".3f") - print() + result = clf(text) + top = result[0][0] if result and result[0] else {'label': 'unknown', 'score': 0.0} + print(f"Text: {text}") + print(f" → {top['label']} ({top['score']:.3f})\n")
35-49: Prefer torch dtype, not string.Passing "float32" as a string may be ignored; use torch.float32.
+ import torch @@ - model_kwargs={ - "torch_dtype": "float32", + model_kwargs={ + "torch_dtype": torch.float32, "use_safetensors": True, # Force safetensors "ignore_mismatched_sizes": True # Try to handle size mismatches }scripts/deployment/deploy_deberta_model.py (1)
78-91: Use torch.float32 instead of string in pipeline kwargs.- clf = pipeline( + import torch + clf = pipeline( "text-classification", @@ - model_kwargs={ - "torch_dtype": "float32", + model_kwargs={ + "torch_dtype": torch.float32, "use_safetensors": True, "ignore_mismatched_sizes": True }scripts/testing/deberta_workaround.py (4)
80-87: Be resilient to minor key mismatches when loading state dictStrict=True may fail on harmless missing/extra keys. Capture load report for diagnostics.
- model.load_state_dict(state_dict) + missing, unexpected = model.load_state_dict(state_dict, strict=False) + if missing or unexpected: + print(f"⚠️ load_state_dict: missing={len(missing)} unexpected={len(unexpected)}")
142-147: Fix placeholder prints; show label and score- print(f"Text: {text}") - print(".3f") - print() + print(f"Text: {text}") + print(f"→ {result['label']} ({result['score']:.3f})") + print()
51-53: Avoid blind except; include traceback for debuggingAt minimum, log full exception details.
- except Exception as e: - print(f"❌ Failed to download {filename}: {e}") + except Exception as e: + import traceback; traceback.print_exc() + print(f"❌ Failed to download {filename}: {e}") return None- except Exception as e: - print(f"❌ Manual loading failed: {e}") + except Exception as e: + import traceback; traceback.print_exc() + print(f"❌ Manual loading failed: {e}") return NoneAlso applies to: 91-93
1-1: Shebang present but file may not be executableEither chmod +x in repo or drop the shebang.
scripts/testing/debug_deberta_loading.py (3)
169-176: Silence unused loop/index variables- for i in range(num_runs): + for _ in range(num_runs): @@ - results = model.predict_emotions(text, threshold=0.5) + _ = model.predict_emotions(text, threshold=0.5) @@ - results = model(text) + _ = model(text)
67-69: Avoid blind except; add traceback for diagnosis- except Exception as e: - print(f"❌ Method 1 failed: {e}") + except Exception as e: + import traceback; traceback.print_exc() + print(f"❌ Method 1 failed: {e}") return None(Apply the same pattern to Methods 2 and 3, and the production model try/except.)
Also applies to: 144-147, 202-204, 230-232
1-1: Shebang present but file may not be executableEither chmod +x or drop the shebang.
scripts/testing/deberta_journal_inference_demo.py (3)
124-131: Prefer label→prob mapping for all_probabilities- "all_probabilities": {str(i): float(prob) for i, prob in enumerate(probabilities)}, + "all_probabilities": { + (self.model.config.id2label[i] if hasattr(self.model.config, "id2label") and i in self.model.config.id2label + else (self.emotion_labels[i] if i < len(self.emotion_labels) else str(i))): float(prob) + for i, prob in enumerate(probabilities) + },
303-304: PEP 484: make filename Optional[str] and import Optional-from typing import List, Dict, Any +from typing import List, Dict, Any, Optional @@ - def save_results(results: Dict[str, Any], filename: str = None) -> str: + def save_results(results: Dict[str, Any], filename: Optional[str] = None) -> str:Also applies to: 16-17
1-1: Shebang present but file may not be executableEither chmod +x or drop the shebang.
scripts/testing/model_comparison_test.py (3)
169-176: Silence unused loop/index variables- for i in range(num_runs): + for _ in range(num_runs): @@ - results = model.predict_emotions(text, threshold=0.5) + _ = model.predict_emotions(text, threshold=0.5) @@ - results = model(text) + _ = model(text)
391-396: Log traceback on failure- logger.error(f"❌ Benchmark failed: {e}") + logger.exception("❌ Benchmark failed")
1-1: Shebang present but file may not be executableEither chmod +x or drop the shebang.
scripts/testing/comprehensive_journal_inference_demo.py (3)
121-129: Map all_probabilities using label names safely- "all_probabilities": {self.emotion_labels[i]: float(prob) - for i, prob in enumerate(probabilities)}, + "all_probabilities": { + (self.emotion_labels[i] if i < len(self.emotion_labels) else str(i)): float(prob) + for i, prob in enumerate(probabilities) + },
274-279: PEP 484: make filename Optional[str] and import Optional-from typing import List, Dict, Any +from typing import List, Dict, Any, Optional @@ - def save_results(results: Dict[str, Any], filename: str = None) -> str: + def save_results(results: Dict[str, Any], filename: Optional[str] = None) -> str:Also applies to: 15-16
1-1: Shebang present but file may not be executableEither chmod +x or drop the shebang.
scripts/start_api_server.py (2)
101-104: Avoid hardcoded docs URLs; honor host/portThe log lines always show port 8000; use the actual CLI values.
Apply:
- logger.info("📖 API Documentation: http://localhost:8000/docs") - logger.info("🔄 ReDoc Documentation: http://localhost:8000/redoc") - logger.info("💚 Health Check: http://localhost:8000/health") + logger.info("📖 API Documentation: http://%s:%s/docs", args.host, args.port) + logger.info("🔄 ReDoc Documentation: http://%s:%s/redoc", args.host, args.port) + logger.info("💚 Health Check: http://%s:%s/health", args.host, args.port)
108-112: Uselogger.exceptionfor unexpected failuresPreserves stack trace; no need to interpolate the exception.
Apply:
- except Exception as e: - logger.error(f"❌ Failed to start server: {e}") + except Exception: + logger.exception("❌ Failed to start server") sys.exit(1)scripts/testing/test_deberta_isolated.py (1)
70-72: Uselogger.exceptionwithout interpolatingeThis logs the stack trace and avoids redundant interpolation.
Apply:
- except Exception as e: - logger.exception(f"❌ DeBERTa isolated test failed: {e}") + except Exception: + logger.exception("❌ DeBERTa isolated test failed") return FalseDEBERTA_INTEGRATION_CODE_REVIEW_FIXES.md (2)
81-88: Add language to fenced code blockHelps linters (MD040) and rendering.
Apply:
-``` +```text Cloud Run Service: samo-emotion-deberta-71517823771.us-central1.run.app ├── Health Check: /health (fixed) ├── Prediction: /api/predict (working) ├── Batch Prediction: /api/predict/batch (working) ├── Admin Status: /admin/model/status (working) └── Security Status: /admin/security/status (working)--- `132-137`: **Avoid secret scanners false-positives in curl example** Use angle brackets to indicate placeholders so tools don’t flag tokens. Apply: ```diff - -H "X-API-Key: YOUR_API_KEY" \ + -H "X-API-Key: <YOUR_API_KEY>" \scripts/testing/quick_model_test.py (1)
64-67: Consider narrowing broad exception handlers in testsCatching
Exceptionobscures root causes; narrow when feasible (e.g.,OSError,HTTPError,RuntimeError) or re-raise after logging.Also applies to: 118-121, 203-206
scripts/testing/test_model_switching.py (1)
28-31: Reload module in production test too (env toggle safety)To ensure
USE_DEBERTA=falseis honored ifmodel_utilswas previously imported.Apply:
# Import and test sys.path.insert(0, str(project_root / 'deployment' / 'cloud-run')) - import model_utils + import importlib, model_utils + importlib.reload(model_utils)configs/samo_emotion_detection_config.yaml (2)
25-31: Rename “BERT” wording for clarityThis config targets DeBERTa; update comments to avoid confusion.
Apply:
- # BERT configuration + # Backbone configuration (DeBERTa) @@ - # Dropout for BERT hidden layers + # Dropout for encoder hidden layers
12-16: Trim trailing spaces flagged by YAML lintersMultiple lines contain trailing whitespace. Please strip across the file to satisfy linters (YAMLlint).
If you prefer a minimal patch, here are representative fixes:
num_emotions: 28 - + # Prediction threshold for binary classification prediction_threshold: 0.6 # Updated from 0.5 for better calibration - + # Temperature scaling for calibrated predictions temperature: 1.0 - + @@ - # Evaluation threshold (lowered to capture more predictions) + # Evaluation threshold (lowered to capture more predictions) threshold: 0.2 @@ - save_best_metric: "f1_macro" + save_best_metric: "f1_macro"Tip: add a pre-commit hook to auto-trim trailing whitespace on YAML.
Also applies to: 84-86, 96-96, 105-108
tests/test_unified_api_server.py (1)
128-164: Edge/validation tests: ensure deterministic multi-emotion outputsGiven mocks in the fixture, assert against known outputs instead of relying on real models which can be flaky.
Consider tightening assertions to the mocked payload (e.g., emotions == ["joy","optimism"]) and injecting custom mocks per-case when needed.
Also applies to: 165-189
src/models/unified_api_server.py (2)
232-236: Use logger.exception and preserve exception chaining for better diagnostics.Apply across endpoints.
- except Exception as e: - logger.error(f"Summarization error: {e}") - raise HTTPException( - status_code=500, detail=f"Summarization failed: {str(e)}" - ) + except Exception as e: + logger.exception("Summarization error") + raise HTTPException(status_code=500, detail=f"Summarization failed: {e}") from eAlso applies to: 304-308, 343-347, 461-465
36-41: Verify import paths are package-consistent.These imports assume top-level packages (“summarization”, “voice_processing”, “emotion_detection”). If this module is under src/models/, prefer relative imports or ensure PYTHONPATH includes src. Example:
-from summarization.t5_summarizer import create_t5_summarizer -from voice_processing.whisper_transcriber import create_whisper_transcriber -from emotion_detection.samo_bert_emotion_classifier import ( +from .summarization.t5_summarizer import create_t5_summarizer +from .voice_processing.whisper_transcriber import create_whisper_transcriber +from .emotion_detection.samo_bert_emotion_classifier import ( create_samo_bert_emotion_classifier )Confirm tests/deployment still resolve after the change.
test_samo_emotion_detection_standalone.py (1)
99-121: Consider asserting error types for invalid inputs (optional).If converting this into a real test suite, assert expected exceptions or error messages instead of printing.
- try: - results = model.predict_emotions(invalid_input, threshold=0.3) - print(f" Result: {results}") - except Exception as e: - print(f" Expected error: {type(e).__name__}: {e}") + with pytest.raises(Exception): + model.predict_emotions(invalid_input, threshold=0.3)deployment/cloud-run/model_utils.py (5)
121-176: Avoid magic number threshold; make it configurable.Expose via env with a sensible default.
-def _predict_emotions_deberta(text: str) -> List[Dict[str, Any]]: +def _predict_emotions_deberta(text: str) -> List[Dict[str, Any]]: ... - for i, score in enumerate(predictions): - score_val = float(score) - if score_val > 0.05: # Only include significant emotions + import os + min_score = float(os.getenv("DEBERTA_SCORE_THRESHOLD", "0.05")) + for i, score in enumerate(predictions): + score_val = float(score) + if score_val > min_score: emotions.append({
330-339: Prefer tempfile over fixed /tmp paths for downloads.Avoid collisions and tighten perms.
- download_dir = f"/tmp/{model_name.replace('/', '_')}" + import tempfile + download_dir = tempfile.mkdtemp(prefix=model_name.replace('/', '_') + "_")
51-53: Comment mismatch with label count.Comment says “6 classes” but list has 7. Update comment to avoid confusion.
-# Emotion labels for the HF emotion model (6 classes) +# Emotion labels for the HF emotion model (7 classes)
378-465: Minor logging polish: use logger.exception without redundant '%s' arg.Example:
- logger.exception("❌ Failed to load emotion model: %s", e) + logger.exception("❌ Failed to load emotion model")Also applies to: 581-588
43-49: Single-source emotion labels across services.Ensure the labels served by /api/emotions, the pipeline outputs, and any docs pull from emotion_labels_runtime (or a central module) to avoid client breakage when switching models.
Also applies to: 350-366, 467-482
src/models/emotion_detection/samo_bert_emotion_classifier.py (3)
268-275: Avoid hardcoded labels; import from central labels module.Keeps taxonomy consistent.
- # Define descriptive emotion labels (28 emotions) - emotion_labels = [ - "admiration", "amusement", "anger", "annoyance", "approval", "caring", - "confusion", "curiosity", "desire", "disappointment", "disapproval", - "disgust", "embarrassment", "excitement", "fear", "gratitude", "grief", - "joy", "love", "nervousness", "optimism", "pride", "realization", - "relief", "remorse", "sadness", "surprise", "neutral" - ] + from .emotion_labels import get_all_emotions + emotion_labels = get_all_emotions()
337-360: Prefer BCEWithLogits for numerical stability (optional).Avoids sigmoid + BCE double pass.
- # Apply sigmoid to get probabilities - probabilities = torch.sigmoid(logits) - - # Compute BCE loss - bce_loss = F.binary_cross_entropy( - probabilities, targets.float(), reduction="none" - ) + targets_f = targets.float() + bce_loss = F.binary_cross_entropy_with_logits( + logits, targets_f, reduction="none" + )
293-297: set_temperature on registered buffer.Avoid .data; use in-place fill for buffers.
- self.temperature.data.fill_(temperature) + self.temperature.fill_(temperature)
| # Core FastAPI and web framework | ||
| fastapi==0.104.1 | ||
| uvicorn[standard]==0.24.0 | ||
| pydantic==2.5.0 | ||
|
|
||
| # Machine Learning and Transformers | ||
| torch>=2.0.0 | ||
| transformers>=4.35.0 | ||
| datasets>=2.15.0 | ||
| accelerate>=0.24.0 | ||
|
|
||
| # Audio processing for Whisper | ||
| openai-whisper>=20231117 | ||
| pydub>=0.25.1 | ||
| librosa>=0.10.0 | ||
|
|
||
| # Scientific computing | ||
| numpy>=1.24.0 | ||
| scipy>=1.11.0 | ||
|
|
||
| # Data processing and ML | ||
| scikit-learn>=1.3.0 | ||
| pandas>=2.1.0 | ||
|
|
||
| # Configuration and utilities | ||
| pyyaml>=6.0 | ||
| python-multipart>=0.0.6 | ||
|
|
||
| # Note: Development and testing dependencies moved to requirements-dev.txt | ||
|
|
||
| # Logging and monitoring | ||
| structlog>=23.2.0 | ||
|
|
||
| # Optional: GPU support (uncomment if needed) | ||
| # torch-audio>=2.0.0 # For better audio processing on GPU | ||
|
|
||
| # Optional: Model optimization | ||
| # onnxruntime>=1.16.0 # For ONNX model inference | ||
| # optimum>=1.14.0 # For optimized transformers No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Runtime breakage risk: missing gunicorn/safetensors and protobuf pin.
- Dockerfile.deberta runs Gunicorn; not listing it will crash the container.
- Code forces use_safetensors=True; missing safetensors will fail at import/load.
- Several scripts rely on protobuf 3.20.3; not pinning invites regressions.
Apply:
# Core FastAPI and web framework
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
+gunicorn>=21.2.0
@@
# Machine Learning and Transformers
torch>=2.0.0
transformers>=4.35.0
datasets>=2.15.0
accelerate>=0.24.0
+safetensors>=0.4.2
@@
# Configuration and utilities
pyyaml>=6.0
python-multipart>=0.0.6
+protobuf==3.20.3Also consider pinning all runtime deps with exact versions or a constraints file for reproducible builds.
Run to confirm discrepancies between Dockerfile and requirements:
🏁 Script executed:
#!/bin/bash
# Check for Gunicorn usage in Dockerfiles vs requirements
rg -n "gunicorn|UvicornWorker" -g "deployment/**/Dockerfile*"
echo "----"
rg -n "^gunicorn" dependencies/requirements-*.txt || echo "Gunicorn not found in requirements"
echo "----"
# Check for safetensors/protobuf pins
rg -n "^safetensors|^protobuf" dependencies/requirements-*.txtLength of output: 1193
Critical: add Gunicorn, safetensors and pin protobuf in runtime requirements
- Multiple Dockerfiles invoke gunicorn but runtime requirements don't list it. Files: deployment/docker/Dockerfile.app:145, deployment/docker/Dockerfile.optimized:63, deployment/docker/Dockerfile.optimized-secure:67, deployment/docker/Dockerfile.emotion_arch_fixed:48, deployment/docker/Dockerfile.production:57, deployment/cloud-run/Dockerfile.deberta:80.
- dependencies/requirements-*.txt contains no safetensors or protobuf pin; add them to avoid import/load and protobuf regressions.
Apply:
# Core FastAPI and web framework
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
+gunicorn>=21.2.0
@@
# Machine Learning and Transformers
torch>=2.0.0
transformers>=4.35.0
datasets>=2.15.0
accelerate>=0.24.0
+safetensors>=0.4.2
@@
# Configuration and utilities
pyyaml>=6.0
python-multipart>=0.0.6
+protobuf==3.20.3Consider pinning runtime deps exactly or using a constraints file for reproducible builds.
📝 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.
| # Core FastAPI and web framework | |
| fastapi==0.104.1 | |
| uvicorn[standard]==0.24.0 | |
| pydantic==2.5.0 | |
| # Machine Learning and Transformers | |
| torch>=2.0.0 | |
| transformers>=4.35.0 | |
| datasets>=2.15.0 | |
| accelerate>=0.24.0 | |
| # Audio processing for Whisper | |
| openai-whisper>=20231117 | |
| pydub>=0.25.1 | |
| librosa>=0.10.0 | |
| # Scientific computing | |
| numpy>=1.24.0 | |
| scipy>=1.11.0 | |
| # Data processing and ML | |
| scikit-learn>=1.3.0 | |
| pandas>=2.1.0 | |
| # Configuration and utilities | |
| pyyaml>=6.0 | |
| python-multipart>=0.0.6 | |
| # Note: Development and testing dependencies moved to requirements-dev.txt | |
| # Logging and monitoring | |
| structlog>=23.2.0 | |
| # Optional: GPU support (uncomment if needed) | |
| # torch-audio>=2.0.0 # For better audio processing on GPU | |
| # Optional: Model optimization | |
| # onnxruntime>=1.16.0 # For ONNX model inference | |
| # optimum>=1.14.0 # For optimized transformers | |
| # Core FastAPI and web framework | |
| fastapi==0.104.1 | |
| uvicorn[standard]==0.24.0 | |
| pydantic==2.5.0 | |
| gunicorn>=21.2.0 | |
| # Machine Learning and Transformers | |
| torch>=2.0.0 | |
| transformers>=4.35.0 | |
| datasets>=2.15.0 | |
| accelerate>=0.24.0 | |
| safetensors>=0.4.2 | |
| # Audio processing for Whisper | |
| openai-whisper>=20231117 | |
| pydub>=0.25.1 | |
| librosa>=0.10.0 | |
| # Scientific computing | |
| numpy>=1.24.0 | |
| scipy>=1.11.0 | |
| # Data processing and ML | |
| scikit-learn>=1.3.0 | |
| pandas>=2.1.0 | |
| # Configuration and utilities | |
| pyyaml>=6.0 | |
| python-multipart>=0.0.6 | |
| protobuf==3.20.3 | |
| # Note: Development and testing dependencies moved to requirements-dev.txt | |
| # Logging and monitoring | |
| structlog>=23.2.0 | |
| # Optional: GPU support (uncomment if needed) | |
| # torch-audio>=2.0.0 # For better audio processing on GPU | |
| # Optional: Model optimization | |
| # onnxruntime>=1.16.0 # For ONNX model inference | |
| # optimum>=1.14.0 # For optimized transformers |
🤖 Prompt for AI Agents
In dependencies/requirements-api.txt around lines 4 to 42, the runtime
requirements are missing gunicorn (needed by multiple Dockerfiles) and do not
include safetensors or a pinned protobuf to avoid import/load and protobuf
regressions; update this file to add gunicorn (e.g., gunicorn==20.1.0 or your
standard project pin), add safetensors (e.g., safetensors>=0.3.0) and pin
protobuf to a known-good version (e.g., protobuf==4.23.4 or your chosen stable
pin), and consider pinning other runtime deps exactly or switching to a
constraints file for reproducible builds so Docker builds and runtime imports
are deterministic.
| curl -X POST http://localhost:8000/detect-emotions \\ | ||
| -H "Content-Type: application/json" \\ | ||
| -d '{"text": "I am feeling happy today!"}' | ||
| """ |
There was a problem hiding this comment.
Usage example targets obsolete endpoint; fix to /api/predict and include API key header.
- # Test deployment
- curl -X POST http://localhost:8000/detect-emotions \\
- -H "Content-Type: application/json" \\
- -d '{"text": "I am feeling happy today!"}'
+ # Test deployment
+ curl -X POST http://localhost:8000/api/predict \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: $API_KEY" \
+ -d '{"text":"I am feeling happy today!"}'📝 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.
| curl -X POST http://localhost:8000/detect-emotions \\ | |
| -H "Content-Type: application/json" \\ | |
| -d '{"text": "I am feeling happy today!"}' | |
| """ | |
| # Test deployment | |
| curl -X POST http://localhost:8000/api/predict \ | |
| -H "Content-Type: application/json" \ | |
| -H "X-API-Key: $API_KEY" \ | |
| -d '{"text":"I am feeling happy today!"}' | |
| """ |
🤖 Prompt for AI Agents
In scripts/deployment/deploy_deberta_model.py around lines 14 to 17, the usage
example calls the obsolete /detect-emotions endpoint and omits the API key;
update the curl example to POST to /api/predict and add the required API key
header (e.g., -H "x-api-key: <API_KEY>"), keep the Content-Type header and the
same JSON body, and ensure the example shows replacing <API_KEY> with a real
key.
| ### 3. Deploy to Cloud Run | ||
| ```bash | ||
| # Build and deploy | ||
| gcloud builds submit --config cloudbuild.yaml | ||
|
|
||
| # Or using Docker | ||
| docker build -f deployment/docker/Dockerfile.optimized -t samo-deberta . | ||
| docker run -p 8080:8080 samo-deberta | ||
| ``` |
There was a problem hiding this comment.
Fix deployment instructions: Cloud Build path, Dockerfile path, endpoint, JSON, and header.
-### 3. Deploy to Cloud Run
+### 3. Deploy to Cloud Run
```bash
-# Build and deploy
-gcloud builds submit --config cloudbuild.yaml
+# Build and deploy
+gcloud builds submit --config deployment/cloud-run/cloudbuild.yaml
@@
-# Or using Docker
-docker build -f deployment/docker/Dockerfile.optimized -t samo-deberta .
-docker run -p 8080:8080 samo-deberta
+# Or using Docker (local run)
+docker build -f deployment/cloud-run/Dockerfile.deberta -t samo-deberta .
+docker run -p 8080:8080 -e PORT=8080 -e ADMIN_API_KEY=${API_KEY:-test123} samo-deberta@@
-# Test emotion detection
-curl -X POST http://localhost:8080/detect-emotions \
-
-H "Content-Type: application/json" \\ -
-d '{{"text": "I am feeling happy today!"}}'
+# Test emotion detection
+curl -X POST http://localhost:8080/api/predict \
-
-H "Content-Type: application/json" \ -
-H "X-API-Key: ${API_KEY:-test123}" \ -
-d '{"text":"I am feeling happy today!"}'
Also applies to: 176-183
<details>
<summary>🤖 Prompt for AI Agents</summary>
In scripts/deployment/deploy_deberta_model.py around lines 165-173 (and also
apply same fixes for lines 176-183), the deployment instructions point to the
wrong Cloud Build config, Dockerfile path, runtime env and API endpoint/headers;
update the gcloud builds submit path to deployment/cloud-run/cloudbuild.yaml,
change the local Docker build to use deployment/cloud-run/Dockerfile.deberta,
run docker with PORT and ADMIN_API_KEY env vars (e.g. -e PORT=8080 -e
ADMIN_API_KEY=${API_KEY:-test123}), and update the curl test to POST to
/api/predict with Content-Type: application/json and X-API-Key header using
${API_KEY:-test123} and a compact JSON body.
</details>
<!-- fingerprinting:phantom:medusa:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
| # Load configuration file if it exists | ||
| config = {} | ||
| if Path(args.config).exists(): | ||
| try: | ||
| import yaml | ||
| with open(args.config, 'r') as f: | ||
| config = yaml.safe_load(f) or {} | ||
| logger.info(f"✅ Loaded configuration from {args.config}") | ||
| except Exception as e: | ||
| logger.warning(f"⚠️ Failed to load config file {args.config}: {e}") | ||
| logger.info("Using default configuration") | ||
|
|
There was a problem hiding this comment.
Initialize logging before using logger in config-loading block
logger is used at Lines 67–70 before it’s defined (Lines 78–79), causing a NameError. Configure logging and create the logger immediately after parsing args, then load YAML (and catch yaml.YAMLError specifically). Also remove the later duplicate basicConfig.
Apply:
@@
args = parser.parse_args()
- # Load configuration file if it exists
- config = {}
- if Path(args.config).exists():
- try:
- import yaml
- with open(args.config, 'r') as f:
- config = yaml.safe_load(f) or {}
- logger.info(f"✅ Loaded configuration from {args.config}")
- except Exception as e:
- logger.warning(f"⚠️ Failed to load config file {args.config}: {e}")
- logger.info("Using default configuration")
-
- # Configure logging
- logging.basicConfig(
- level=getattr(logging, args.log_level.upper()),
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
- )
-
- logger = logging.getLogger(__name__)
+ # Configure logging early
+ logging.basicConfig(
+ level=getattr(logging, args.log_level.upper()),
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ )
+ logger = logging.getLogger("samo.startup")
+
+ # Load configuration file if it exists
+ config = {}
+ if Path(args.config).exists():
+ try:
+ import yaml
+ with open(args.config, 'r') as f:
+ config = yaml.safe_load(f) or {}
+ logger.info("✅ Loaded configuration from %s", args.config)
+ except yaml.YAMLError as e:
+ logger.warning("⚠️ Failed to parse YAML %s: %s", args.config, e)
+ logger.info("Using default configuration")Also applies to: 72-79
🧰 Tools
🪛 Ruff (0.12.2)
67-67: Undefined name logger
(F821)
68-68: Do not catch blind exception: Exception
(BLE001)
69-69: Undefined name logger
(F821)
70-70: Undefined name logger
(F821)
🤖 Prompt for AI Agents
In scripts/start_api_server.py around lines 60 to 79, logger is referenced while
not yet created which causes a NameError; move logging configuration and logger
creation to immediately after argument parsing and before the config-loading
block, replace the broad Exception in the config loader with a yaml.YAMLError
catch (and optionally log other unexpected exceptions separately), and remove
the later duplicate logging.basicConfig call so logging is configured only once;
ensure the loader still sets config = {} when YAML returns None and that
success/failure messages use the newly created logger.
| response = requests.get(url, timeout=10) | ||
|
|
||
| if response.status_code == 200: | ||
| print("✅ Service is responding (HTTP 200)") | ||
| return True | ||
| print(f"⚠️ Service responding but not healthy (HTTP {response.status_code})") | ||
| return False | ||
|
|
There was a problem hiding this comment.
Health check should hit the health endpoint (and optionally use API key).
Root URL may 404; align with API’s GET /api/health.
- # Test root endpoint
- response = requests.get(url, timeout=10)
+ # Test explicit health endpoint
+ health_url = f"{url.rstrip('/')}/api/health"
+ headers = {}
+ api_key = os.getenv("API_KEY") or os.getenv("SAMO_API_KEY")
+ if api_key:
+ headers["Authorization"] = f"Bearer {api_key}"
+ response = requests.get(health_url, headers=headers, timeout=10)
- if response.status_code == 200:
+ if response.status_code == 200:
print("✅ Service is responding (HTTP 200)")
return TrueCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.12.2)
60-60: Consider moving this statement to an else block
(TRY300)
🤖 Prompt for AI Agents
In scripts/testing/cloud_run_deployment_monitor.py around lines 54 to 61, the
health check currently calls the root URL which may 404; change it to call the
API health endpoint (append /api/health to the base URL), and if an API key is
provided send it as the appropriate header (e.g. Authorization: Bearer <API_KEY>
or x-api-key depending on the service) when making the GET request, then treat
HTTP 200 as healthy and non-200 as unhealthy while preserving the timeout and
error handling.
| # Configuration - will be updated when user provides the Cloud Run URL | ||
| config = TestConfig( | ||
| cloud_run_url="https://YOUR-CLOUD-RUN-URL", # To be updated | ||
| num_runs=10, | ||
| num_concurrent=5, | ||
| timeout_seconds=30, | ||
| confidence_level=0.95 | ||
| ) | ||
|
|
||
| tester = ScientificCloudRunTester(config) | ||
|
|
There was a problem hiding this comment.
Honor CLOUD_RUN_URL env var (set by the deployment monitor).
Without this, trigger_comprehensive_testing won’t point the tester at the deployed service.
config = TestConfig(
cloud_run_url="https://YOUR-CLOUD-RUN-URL", # To be updated
num_runs=10,
num_concurrent=5,
timeout_seconds=30,
confidence_level=0.95
)
+ # Override via environment (preferred in CI/CD and deployment monitor)
+ env_url = os.getenv("CLOUD_RUN_URL")
+ if env_url:
+ config.cloud_run_url = env_url
+
tester = ScientificCloudRunTester(config)📝 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.
| # Configuration - will be updated when user provides the Cloud Run URL | |
| config = TestConfig( | |
| cloud_run_url="https://YOUR-CLOUD-RUN-URL", # To be updated | |
| num_runs=10, | |
| num_concurrent=5, | |
| timeout_seconds=30, | |
| confidence_level=0.95 | |
| ) | |
| tester = ScientificCloudRunTester(config) | |
| # Configuration - will be updated when user provides the Cloud Run URL | |
| config = TestConfig( | |
| cloud_run_url="https://YOUR-CLOUD-RUN-URL", # To be updated | |
| num_runs=10, | |
| num_concurrent=5, | |
| timeout_seconds=30, | |
| confidence_level=0.95 | |
| ) | |
| # Override via environment (preferred in CI/CD and deployment monitor) | |
| env_url = os.getenv("CLOUD_RUN_URL") | |
| if env_url: | |
| config.cloud_run_url = env_url | |
| tester = ScientificCloudRunTester(config) |
🤖 Prompt for AI Agents
In scripts/testing/scientific_cloud_run_testing.py around lines 456 to 466, the
TestConfig is hardcoded to "https://YOUR-CLOUD-RUN-URL" which ignores the
CLOUD_RUN_URL environment variable set by the deployment monitor; change the
code to read os.environ.get("CLOUD_RUN_URL") and pass that value (with a
sensible fallback to the existing placeholder or raise/log an error if missing)
into TestConfig.cloud_run_url, and ensure the module imports os at the top of
the file.
| def __init__( | ||
| self, | ||
| texts: List[str], | ||
| labels: List[List[int]], | ||
| tokenizer: AutoTokenizer, | ||
| max_length: int = 512, | ||
| ) -> None: | ||
| """ | ||
| Initialize emotion dataset. | ||
|
|
||
| Args: | ||
| texts: List of text samples | ||
| labels: List of label lists (multi-label) | ||
| tokenizer: BERT tokenizer | ||
| max_length: Maximum sequence length | ||
| """ | ||
| self.texts = texts | ||
| self.labels = labels | ||
| self.tokenizer = tokenizer | ||
| self.max_length = max_length | ||
|
|
There was a problem hiding this comment.
Bug: EmotionDataset uses self.num_emotions but never sets it.
Will raise at runtime. Set num_emotions in init (derive from labels if needed).
class EmotionDataset(Dataset):
@@
- def __init__(
+ def __init__(
self,
texts: List[str],
labels: List[List[int]],
- tokenizer: AutoTokenizer,
+ tokenizer: AutoTokenizer,
max_length: int = 512,
) -> None:
@@
- self.max_length = max_length
+ self.max_length = max_length
+ # Infer number of emotions from first label vector
+ self.num_emotions = len(labels[0]) if labels else 0Also applies to: 405-411
🤖 Prompt for AI Agents
In src/models/emotion_detection/samo_bert_emotion_classifier.py around lines
366-386 (and also apply same fix to 405-411), the EmotionDataset __init__ never
sets self.num_emotions which will cause runtime errors; set self.num_emotions
during initialization by deriving it from the labels (e.g., if labels is
non-empty set self.num_emotions = len(labels[0]) else set it to 0), and add a
simple validation/assertion that all label vectors have the same length (raise a
clear error if not) so num_emotions is consistent.
| return EmotionDetectionResponse( | ||
| emotions=results["emotions"][0] | ||
| if results["emotions"] else [], | ||
| probabilities=results["probabilities"][0] | ||
| if results["probabilities"] else [], | ||
| predictions=results["predictions"][0] | ||
| if results["predictions"] else [], | ||
| processing_time=processing_time, |
There was a problem hiding this comment.
Schema mismatch: cast predictions to int for response model.
predictions are floats (0.0/1.0). Pydantic model expects List[int]. Fix in both endpoints.
- predictions=results["predictions"][0]
- if results["predictions"] else [],
+ predictions=[int(x) for x in results["predictions"][0]]
+ if results["predictions"] else [],- predictions=emotion_results["predictions"][0]
- if emotion_results["predictions"] else [],
+ predictions=[int(x) for x in emotion_results["predictions"][0]]
+ if emotion_results["predictions"] else [],Also applies to: 425-432
🤖 Prompt for AI Agents
In src/models/unified_api_server.py around lines 328-335 (and similarly at
425-432), the response is returning predictions as floats (0.0/1.0) while the
Pydantic EmotionDetectionResponse expects List[int]; update both endpoints to
cast/convert prediction values to ints before constructing the response (e.g.,
map/int() each element of results["predictions"][0] when non-empty, and return
an empty list otherwise), preserving existing empty-list handling and other
fields.
| def test_transcribe_endpoint_success(self, mock_create_transcriber, client): | ||
| """Test successful audio transcription with mocked transcriber.""" | ||
| # Mock the transcriber | ||
| mock_transcriber = Mock() | ||
| mock_result = Mock() | ||
| mock_result.text = "This is a test transcription" | ||
| mock_result.language = "en" | ||
| mock_result.confidence = 0.95 | ||
| mock_result.duration = 10.5 | ||
| mock_result.processing_time = 2.1 | ||
| mock_result.audio_quality = "excellent" | ||
| mock_result.word_count = 5 | ||
| mock_result.speaking_rate = 150.0 | ||
| mock_result.no_speech_probability = 0.1 | ||
|
|
||
| mock_transcriber.transcribe.return_value = mock_result | ||
| mock_create_transcriber.return_value = mock_transcriber | ||
|
|
There was a problem hiding this comment.
Mock MIME detection for success path
python-magic inspects bytes; fake payloads won’t be recognized as audio. Stub it to return an allowed MIME.
Apply:
- @patch('src.models.unified_api_server.create_whisper_transcriber')
- def test_transcribe_endpoint_success(self, mock_create_transcriber, client):
+ @patch('src.models.unified_api_server.create_whisper_transcriber')
+ @patch('src.models.unified_api_server.magic.Magic')
+ def test_transcribe_endpoint_success(self, mock_magic, mock_create_transcriber, client):
@@
- # Create a fake audio file
+ # Stub MIME detection
+ mock_magic.return_value.from_buffer.return_value = "audio/mpeg"
+ # Create a fake audio file📝 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.
| def test_transcribe_endpoint_success(self, mock_create_transcriber, client): | |
| """Test successful audio transcription with mocked transcriber.""" | |
| # Mock the transcriber | |
| mock_transcriber = Mock() | |
| mock_result = Mock() | |
| mock_result.text = "This is a test transcription" | |
| mock_result.language = "en" | |
| mock_result.confidence = 0.95 | |
| mock_result.duration = 10.5 | |
| mock_result.processing_time = 2.1 | |
| mock_result.audio_quality = "excellent" | |
| mock_result.word_count = 5 | |
| mock_result.speaking_rate = 150.0 | |
| mock_result.no_speech_probability = 0.1 | |
| mock_transcriber.transcribe.return_value = mock_result | |
| mock_create_transcriber.return_value = mock_transcriber | |
| @patch('src.models.unified_api_server.create_whisper_transcriber') | |
| @patch('src.models.unified_api_server.magic.Magic') | |
| def test_transcribe_endpoint_success(self, mock_magic, mock_create_transcriber, client): | |
| """Test successful audio transcription with mocked transcriber.""" | |
| # Mock the transcriber | |
| mock_transcriber = Mock() | |
| mock_result = Mock() | |
| mock_result.text = "This is a test transcription" | |
| mock_result.language = "en" | |
| mock_result.confidence = 0.95 | |
| mock_result.duration = 10.5 | |
| mock_result.processing_time = 2.1 | |
| mock_result.audio_quality = "excellent" | |
| mock_result.word_count = 5 | |
| mock_result.speaking_rate = 150.0 | |
| mock_result.no_speech_probability = 0.1 | |
| mock_transcriber.transcribe.return_value = mock_result | |
| mock_create_transcriber.return_value = mock_transcriber | |
| # Stub MIME detection | |
| mock_magic.return_value.from_buffer.return_value = "audio/mpeg" | |
| # Create a fake audio file |
🤖 Prompt for AI Agents
In tests/test_unified_api_server.py around lines 214 to 231, the test uses fake
audio bytes but python-magic will not recognize them as audio; patch the MIME
detection used in your code (e.g., magic.from_buffer or the module path you
import) to return an allowed audio MIME like "audio/wav" (or "audio/mpeg") for
the success path; add a mock or monkeypatch before calling the endpoint so the
MIME check passes (ensure you patch the exact import path used by the server
code and restore/unapply after the test if needed).
| @patch('src.models.unified_api_server.create_whisper_transcriber') | ||
| @patch('src.models.unified_api_server.create_t5_summarizer') | ||
| @patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') | ||
| def test_combined_processing_success(self, mock_emotion_detector, mock_summarizer, | ||
| mock_transcriber, client): | ||
| """Test successful combined audio processing with mocked models.""" |
There was a problem hiding this comment.
Mock MIME detection in combined pipeline test as well
Prevents 400 from MIME check on fake bytes.
Apply:
- @patch('src.models.unified_api_server.create_whisper_transcriber')
+ @patch('src.models.unified_api_server.create_whisper_transcriber')
@patch('src.models.unified_api_server.create_t5_summarizer')
@patch('src.models.unified_api_server.create_samo_bert_emotion_classifier')
- def test_combined_processing_success(self, mock_emotion_detector, mock_summarizer,
- mock_transcriber, client):
+ @patch('src.models.unified_api_server.magic.Magic')
+ def test_combined_processing_success(self, mock_magic, mock_emotion_detector, mock_summarizer,
+ mock_transcriber, client):
@@
- # Create fake audio file
+ # Stub MIME detection
+ mock_magic.return_value.from_buffer.return_value = "audio/mpeg"
+ # Create fake audio file📝 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.
| @patch('src.models.unified_api_server.create_whisper_transcriber') | |
| @patch('src.models.unified_api_server.create_t5_summarizer') | |
| @patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') | |
| def test_combined_processing_success(self, mock_emotion_detector, mock_summarizer, | |
| mock_transcriber, client): | |
| """Test successful combined audio processing with mocked models.""" | |
| @patch('src.models.unified_api_server.create_whisper_transcriber') | |
| @patch('src.models.unified_api_server.create_t5_summarizer') | |
| @patch('src.models.unified_api_server.create_samo_bert_emotion_classifier') | |
| @patch('src.models.unified_api_server.magic.Magic') | |
| def test_combined_processing_success(self, mock_magic, mock_emotion_detector, mock_summarizer, | |
| mock_transcriber, client): | |
| """Test successful combined audio processing with mocked models.""" | |
| # Stub MIME detection | |
| mock_magic.return_value.from_buffer.return_value = "audio/mpeg" | |
| # Create fake audio file |
🤖 Prompt for AI Agents
In tests/test_unified_api_server.py around lines 258-263, the combined pipeline
test fails MIME validation because it sends fake byte content; add a mock for
the MIME-detection call used by unified_api_server (e.g., patch
'src.models.unified_api_server.detect_mime' or the exact function used to check
MIME) to return a valid audio MIME type (like 'audio/wav') for the fake bytes,
and set that mock's return_value before invoking the endpoint so the test
bypasses the 400 MIME check.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (18)
scripts/testing/cloud_run_deployment_monitor.py (4)
72-96: Probe multiple candidate endpoints to avoid route drift.Try a prioritized list and pick the first healthy endpoint.
@staticmethod def test_api_endpoint(url: str) -> bool: - """Test the actual API endpoint with a sample request.""" + """Test the actual API endpoint with a sample request (robust probing).""" try: - api_url = f"{url.rstrip('/')}/api/predict" + candidates = ["/api/predict", "/predict", "/analyze"] + api_url = None + for path in candidates: + trial = f"{url.rstrip('/')}{path}" + resp = requests.options(trial, timeout=10) + if resp.status_code in (200, 204, 405): + api_url = trial + break + if not api_url: + print("⚠️ No viable predict endpoint discovered") + return False
9-15: Import os (used later for env copy).Prevents NameError at Line 168 when copying environment.
import sys import time import requests import subprocess +import os from pathlib import Path from typing import Optional, Dict, Any
49-61: Health check should call /api/health and validate JSON status.Aligns with secure_api_server’s health contract and avoids false positives.
@staticmethod def test_service_health(url: str) -> bool: """Test if the service is responding and healthy.""" try: - # Test root endpoint - response = requests.get(url, timeout=10) - - if response.status_code == 200: - print("✅ Service is responding (HTTP 200)") - return True + health_url = f"{url.rstrip('/')}/api/health" + response = requests.get(health_url, timeout=10) + + if response.status_code == 200 and response.headers.get("content-type","").startswith("application/json"): + data = response.json() + if data.get("status") == "healthy": + print("✅ Service is healthy") + return True print(f"⚠️ Service responding but not healthy (HTTP {response.status_code})") return False
72-92: Use unified predict endpoint; support API key and new response shape.Switch to /api/predict, honor optional API key, accept emotions list.
@staticmethod def test_api_endpoint(url: str) -> bool: """Test the actual API endpoint with a sample request.""" try: - api_url = f"{url}/analyze" # Assuming standard endpoint + api_url = f"{url.rstrip('/')}/api/predict" payload = {"text": "I feel happy today!"} - headers = {"Content-Type": "application/json"} + headers = {"Content-Type": "application/json"} + api_key = os.getenv("MONITOR_API_KEY") or os.getenv("API_KEY") + if api_key: + headers["X-API-Key"] = api_key response = requests.post(api_url, json=payload, headers=headers, timeout=30) - if response.status_code == 200: + if response.status_code == 200 and response.headers.get("content-type","").startswith("application/json"): data = response.json() - if "primary_emotion" in data: - print("🎯 API endpoint working correctly!") - print(f" Primary emotion: {data.get('primary_emotion', 'unknown')}") - return True + if isinstance(data.get("emotions"), list) and data["emotions"]: + top = data["emotions"][0] + print("🎯 API endpoint working correctly!") + print(f" Top emotion: {top.get('emotion','unknown')} ({top.get('confidence',0):.2f})") + return True + if "primary_emotion" in data: + print("🎯 API endpoint working (legacy shape)") + print(f" Primary emotion: {data.get('primary_emotion', 'unknown')}") + return True print("⚠️ API responding but unexpected response format") return False print(f"❌ API endpoint error (HTTP {response.status_code})") return Falsescripts/start_api_server.py (2)
72-91: Initialize logging before using logger; log exceptions properly.Fix NameError and improve diagnostics.
- # Load configuration file if it exists - config = {} - if Path(args.config).exists(): - try: - import yaml - with open(args.config, 'r') as f: - config = yaml.safe_load(f) or {} - logger.info(f"✅ Loaded configuration from {args.config}") - except Exception as e: - logger.warning(f"⚠️ Failed to load config file {args.config}: {e}") - logger.info("Using default configuration") - - # Configure logging - logging.basicConfig( + # Configure logging early + logging.basicConfig( level=getattr(logging, args.log_level.upper()), format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) + + # Load configuration file if it exists + config = {} + if Path(args.config).exists(): + try: + import yaml + with open(args.config, 'r') as f: + config = yaml.safe_load(f) or {} + logger.info(f"✅ Loaded configuration from {args.config}") + except Exception: + logger.exception(f"⚠️ Failed to load config file {args.config}") + logger.info("Using default configuration")
100-112: Apply CORS env before server init; support api.cors_origins fallback.Ensures SAMOUnifiedAPIServer picks up origins during init.
- # Create and start server with configuration - server = SAMOUnifiedAPIServer() - - # Apply configuration if available - if config: - # Apply server configuration - if 'server' in config: - server_config = config['server'] - if 'cors_origins' in server_config: - os.environ['API_ALLOWED_ORIGINS'] = ','.join(server_config['cors_origins']) - logger.info("✅ Applied server configuration") + # Apply configuration before server init + if config: + server_cfg = (config.get('server') or {}) + api_cfg = (config.get('api') or {}) + cors = server_cfg.get('cors_origins') or api_cfg.get('cors_origins') + if cors: + os.environ['API_ALLOWED_ORIGINS'] = ','.join(cors) + logger.info("✅ Applied server CORS configuration") + + # Create and start server + server = SAMOUnifiedAPIServer()tests/test_unified_api_server.py (4)
24-25: Inline immediately returned variable in fixtureMinor cleanup; return the instance directly.
Apply this diff:
- server = SAMOUnifiedAPIServer() - return server + return SAMOUnifiedAPIServer()
128-164: Stub emotion detector to ensure multi-emotion edge cases are exercised reliablyHitting the real model can return a single emotion or 503 in CI. Use a stub that returns multiple emotions so assertions are stable.
Apply this diff:
- @staticmethod - def test_detect_emotions_edge_cases(client): + def test_detect_emotions_edge_cases(self, api_server, client): @@ - response = client.post("/detect-emotions", json={ + from unittest.mock import Mock + emo = Mock() + emo.device = "cpu" + emo.predict_emotions.return_value = { + "emotions": [["excitement", "nervousness", "sadness"]], + "probabilities": [[0.8, 0.6, 0.35]], + "predictions": [[1, 1, 1]] + } + api_server.models["emotion_detector"] = emo + + response = client.post("/detect-emotions", json={ "text": ambiguous_text, "threshold": 0.3 }) @@ - response = client.post("/detect-emotions", json={ + emo.predict_emotions.return_value = { + "emotions": [["joy"]], + "probabilities": [[0.55]], + "predictions": [[1]] + } + response = client.post("/detect-emotions", json={ "text": short_text, "threshold": 0.3 }) @@ - response = client.post("/detect-emotions", json={ + emo.predict_emotions.return_value = { + "emotions": [["love", "anger", "fear"]], + "probabilities": [[0.72, 0.64, 0.41]], + "predictions": [[1, 1, 1]] + } + response = client.post("/detect-emotions", json={ "text": mixed_text, "threshold": 0.2 })
165-189: Validation test: ensure a model is present before expecting 200 for 10k-char payloadThe last assertion expects 200 but no model is set. Install a simple stub first.
Apply this diff:
- @staticmethod - def test_detect_emotions_endpoint_validation(client): + def test_detect_emotions_endpoint_validation(self, api_server, client): @@ - long_text = "a" * 10000 + # Provide a minimal stub to allow success + from unittest.mock import Mock + emo = Mock() + emo.device = "cpu" + emo.predict_emotions.return_value = { + "emotions": [["neutral"]], + "probabilities": [[0.51]], + "predictions": [[1]] + } + api_server.models["emotion_detector"] = emo + long_text = "a" * 10000 response = client.post("/detect-emotions", json={"text": long_text})
213-236: Fix mocking: inject mock transcriber into running server (api_server.models)The api_server fixture constructs SAMOUnifiedAPIServer (and loads models) before the @patch is applied, so patching create_whisper_transcriber in the test doesn't replace the already-mounted transcriber — assign the mock into api_server.models to make the TestClient use it.
- @patch('src.models.unified_api_server.create_whisper_transcriber') - def test_transcribe_endpoint_success(self, mock_create_transcriber, client): + @patch('src.models.unified_api_server.create_whisper_transcriber') + def test_transcribe_endpoint_success(self, mock_create_transcriber, api_server, client): @@ - mock_create_transcriber.return_value = mock_transcriber + mock_create_transcriber.return_value = mock_transcriber + # Ensure the running server uses the mock (fixture already constructed) + api_server.models["transcriber"] = mock_transcriberdeployment/cloud-run/model_utils.py (5)
262-271: Use torch.float32 in both branches and drop unused max_lengthClean dtype and remove dead assignment.
Apply this diff:
if USE_DEBERTA: model_name = DEBERTA_MODEL_NAME model_kwargs = { - "torch_dtype": "float32", + "torch_dtype": torch.float32, "use_safetensors": True, "ignore_mismatched_sizes": True } - max_length = 256 model_type = "DeBERTa (28 emotions)" @@ else: # PRODUCTION MODEL - use pipeline approach (works fine) model_name = PRODUCTION_MODEL_NAME - model_kwargs = {"torch_dtype": "float32"} - max_length = 512 + model_kwargs = {"torch_dtype": torch.float32} model_type = "Production (6 emotions)"Also applies to: 313-315
69-111: CustomPipeline: honor kwargs and de-duplicate labelsForward kwargs to tokenizer/model and use a single labels constant.
Apply this diff:
+ # DeBERTa emotion labels (single source of truth) + DEBERTA_EMOTION_LABELS = [ + 'admiration','amusement','anger','annoyance','approval','caring', + 'confusion','curiosity','desire','disappointment','disapproval', + 'disgust','embarrassment','excitement','fear','gratitude','grief', + 'joy','love','nervousness','optimism','pride','realization', + 'relief','remorse','sadness','surprise','neutral' + ] class CustomPipeline(TextClassificationPipeline): @@ - def __call__(self, inputs, **kwargs): + def __call__(self, inputs, **kwargs): # Override to handle DeBERTa tokenizer issues if isinstance(inputs, str): inputs = [inputs] @@ - encoded = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256) + encoded = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=256, **kwargs) with torch.no_grad(): - outputs = self.model(**encoded) + outputs = self.model(**encoded) predictions = torch.sigmoid(outputs.logits).squeeze(0) @@ - emotion_labels = [ - 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', - 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', - 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', - 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', - 'relief', 'remorse', 'sadness', 'surprise', 'neutral' - ] + emotion_labels = DEBERTA_EMOTION_LABELS
121-175: De-duplicate emotion labels in direct path and runtime label updateReference the same labels constant to avoid drift.
Apply this diff:
- # DeBERTa emotion labels (28 emotions) - emotion_labels = [ - 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', - 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', - 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', - 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', - 'relief', 'remorse', 'sadness', 'surprise', 'neutral' - ] + emotion_labels = DEBERTA_EMOTION_LABELS @@ - if USE_DEBERTA and emotion_model is not None: - # For DeBERTa, use our predefined labels - emotion_labels_runtime = [ - 'admiration', 'amusement', 'anger', 'annoyance', 'approval', 'caring', - 'confusion', 'curiosity', 'desire', 'disappointment', 'disapproval', - 'disgust', 'embarrassment', 'excitement', 'fear', 'gratitude', 'grief', - 'joy', 'love', 'nervousness', 'optimism', 'pride', 'realization', - 'relief', 'remorse', 'sadness', 'surprise', 'neutral' - ] + if USE_DEBERTA and emotion_model is not None: + emotion_labels_runtime = DEBERTA_EMOTION_LABELSAlso applies to: 349-360
292-299: Use tempfile for download directories instead of hardcoded /tmpImproves portability and security.
Apply this diff:
- download_dir = f"/tmp/{model_name.replace('/', '_')}" + import tempfile, os + download_dir = os.path.join(tempfile.gettempdir(), model_name.replace('/', '_')) @@ - download_dir = f"/tmp/{model_name.replace('/', '_')}" + import tempfile, os + download_dir = os.path.join(tempfile.gettempdir(), model_name.replace('/', '_'))Also applies to: 330-347
256-259: Pass a torch.dtype (not string) to from_pretrainedUsing "float32" as a string can be ignored. Use torch.float32.
Apply this diff:
- model = AutoModelForSequenceClassification.from_pretrained( - EMOTION_MODEL_DIR, local_files_only=True, torch_dtype="float32" - ) + model = AutoModelForSequenceClassification.from_pretrained( + EMOTION_MODEL_DIR, local_files_only=True, torch_dtype=torch.float32 + )src/models/unified_api_server.py (2)
328-341: Cast predictions to int to satisfy response schemapredict_emotions returns float 0/1; Pydantic model expects List[int].
Apply this diff:
return EmotionDetectionResponse( emotions=results["emotions"][0] if results["emotions"] else [], probabilities=results["probabilities"][0] if results["probabilities"] else [], - predictions=results["predictions"][0] - if results["predictions"] else [], + predictions=[int(x) for x in results["predictions"][0]] + if results["predictions"] else [],
369-376: NameError: uuid not imported in process-audio routeThis path uses uuid.uuid4() without importing uuid in this scope.
Apply this diff:
@@ from pathlib import Path from typing import List, Optional, Dict, Any, Union from datetime import datetime +import uuidsrc/models/emotion_detection/samo_bert_emotion_classifier.py (1)
259-263: Guard top_k to avoid runtime errors when k > num classestorch.topk requires k <= number of classes.
Apply this diff:
- if top_k is not None: - _, top_k_indices = torch.topk(probabilities, top_k, dim=1) + if top_k is not None: + k = max(1, min(int(top_k), probabilities.size(1))) + top_k_indices = torch.topk(probabilities, k, dim=1).indices predictions = torch.zeros_like(probabilities) predictions.scatter_(1, top_k_indices, 1.0)
🧹 Nitpick comments (17)
scripts/testing/cloud_run_deployment_monitor.py (1)
154-160: Replace × with x in console text.Avoid ambiguous Unicode in terminals/CI logs.
- print(" 1. ✅ Comprehensive API testing (10 runs × 5 entries)") + print(" 1. ✅ Comprehensive API testing (10 runs x 5 entries)")scripts/download_model.py (4)
7-9: Add Path import for cache sentinel handling (next diff).import os import sys +from pathlib import Path
17-23: Make model name and cache dir configurable via env.Improves reuse across environments and Docker builds.
- # Model configuration - model_name = 'duelker/samo-goemotions-deberta-v3-large' - cache_dir = '/app/models' + # Model configuration (env-overridable) + model_name = os.getenv('DEBERTA_MODEL_NAME', 'duelker/samo-goemotions-deberta-v3-large') + cache_dir = os.getenv('DEBERTA_CACHE_DIR', '/app/models')
27-36: Skip download when cache already primed.Idempotent builds; saves time and bandwidth.
# Create cache directory os.makedirs(cache_dir, exist_ok=True) + # Fast path: skip if already downloaded + sentinel = Path(cache_dir) / ".deberta_download_ok" + if sentinel.exists(): + print("✅ Model already present in cache; skipping download") + return True + # Download tokenizer print("📥 Downloading tokenizer...")
51-52: Write cache sentinel on success.print("🎉 DeBERTa model pre-download completed successfully!") + sentinel.touch() return TrueCODE_REVIEW_IMPROVEMENTS.md (1)
74-79: Avoid committing token-like headers in examples.Use an environment variable placeholder to stop secret scanners from flagging docs.
curl -X POST https://samo-emotion-deberta-71517823771.us-central1.run.app/api/predict \ -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ + -H "X-API-Key: $API_KEY" \ -d '{"text": "I am feeling happy today!"}'deployment/cloud-run/Dockerfile.deberta (1)
69-76: Consider adding HF transfer acceleration.Optional: speeds model downloads during build.
# Health check following Cloud Run best practices HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # Use exec form for CMD (Docker best practice) # Optimized for 2-CPU Cloud Run instance: (2 * cores) + 1 = 5 workers # Set timeout to 0 for Cloud Run (allows unlimited request timeouts) +ENV HF_HUB_ENABLE_HF_TRANSFER=1 CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 5 --threads 2 --timeout 0 --keep-alive 5 --max-requests 1000 --max-requests-jitter 100 --access-logfile - --error-logfile - --log-level info secure_api_server:app"]configs/samo_api_config.yaml (1)
86-86: Add newline at EOF.Silences YAML lint warnings.
scripts/start_api_server.py (1)
120-124: Use logger.exception in generic except.Captures traceback.
- except Exception as e: - logger.error(f"❌ Failed to start server: {e}") + except Exception: + logger.exception("❌ Failed to start server") sys.exit(1)DEBERTA_INTEGRATION_CODE_REVIEW_FIXES.md (2)
129-137: Mask API key in docs to avoid secret-scanner alerts.-# Test prediction endpoint -curl -X POST https://samo-emotion-deberta-71517823771.us-central1.run.app/api/predict \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{"text": "I am feeling happy today!"}' +# Test prediction endpoint +curl -X POST https://samo-emotion-deberta-71517823771.us-central1.run.app/api/predict \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $API_KEY" \ + -d '{"text": "I am feeling happy today!"}'
80-88: Add fenced code language for the diagram block.-``` +```text Cloud Run Service: samo-emotion-deberta-71517823771.us-central1.run.app ├── Health Check: /health (fixed) ├── Prediction: /api/predict (working) ├── Batch Prediction: /api/predict/batch (working) ├── Admin Status: /admin/model/status (working) └── Security Status: /admin/security/status (working)</blockquote></details> <details> <summary>configs/samo_emotion_detection_config.yaml (1)</summary><blockquote> `1-187`: **Trim trailing spaces and add a reproducibility seed** There are many trailing spaces flagged by yamllint. Also consider adding an explicit random seed to make experiments reproducible. Apply this diff (excerpt): ```diff model: name: "duelker/samo-goemotions-deberta-v3-large" # DeBERTa model for emotion understanding device: null + seed: 42And remove trailing spaces throughout the file (no semantic changes).
src/models/unified_api_server.py (2)
274-281: Safer temp-file handling for uploadsPrefer tempfile to avoid hardcoded /tmp and collisions.
Apply this diff:
- unique_suffix = uuid.uuid4().hex - extension = file.filename.split('.')[-1] if '.' in file.filename else '' - temp_path = f"/tmp/{unique_suffix}.{extension}" if extension else f"/tmp/{unique_suffix}" + import tempfile, os + unique_suffix = uuid.uuid4().hex + extension = file.filename.split('.')[-1] if '.' in file.filename else '' + temp_dir = tempfile.gettempdir() + temp_path = os.path.join(temp_dir, f"{unique_suffix}.{extension}") if extension else os.path.join(temp_dir, unique_suffix) @@ - unique_suffix = uuid.uuid4().hex - extension = file.filename.split('.')[-1] if '.' in file.filename else '' - temp_path = f"/tmp/{unique_suffix}.{extension}" if extension else f"/tmp/{unique_suffix}" + import tempfile, os + unique_suffix = uuid.uuid4().hex + extension = file.filename.split('.')[-1] if '.' in file.filename else '' + temp_dir = tempfile.gettempdir() + temp_path = os.path.join(temp_dir, f"{unique_suffix}.{extension}") if extension else os.path.join(temp_dir, unique_suffix)Also applies to: 370-376
37-41: Use absolute package imports to avoid import-path fragilityImport from the installed package path to prevent ImportError when running via
src.models.unified_api_server.Apply this diff:
-from summarization.t5_summarizer import create_t5_summarizer -from voice_processing.whisper_transcriber import create_whisper_transcriber -from emotion_detection.samo_bert_emotion_classifier import ( +from src.models.summarization.t5_summarizer import create_t5_summarizer +from src.models.voice_processing.whisper_transcriber import create_whisper_transcriber +from src.models.emotion_detection.samo_bert_emotion_classifier import ( create_samo_bert_emotion_classifier )test_samo_emotion_detection_standalone.py (1)
61-96: Consider parametrizing loops in tests for clarity and speedMultiple loops print verbose output; pytest parametrize would make failures clearer and reduce noise. Optional.
Also applies to: 148-157, 210-287
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
268-275: Single source of truth for emotion labelsImport labels from the shared module to avoid drift across files.
Apply this diff:
- emotion_labels = [ - "admiration", "amusement", "anger", "annoyance", "approval", "caring", - "confusion", "curiosity", "desire", "disappointment", "disapproval", - "disgust", "embarrassment", "excitement", "fear", "gratitude", "grief", - "joy", "love", "nervousness", "optimism", "pride", "realization", - "relief", "remorse", "sadness", "surprise", "neutral" - ] + from src.models.emotion_detection.emotion_labels import get_all_emotions + emotion_labels = get_all_emotions()
337-355: Use BCEWithLogits for numerical stability and correct weightingAvoid sigmoid + BCE and apply class weights via weight arg on logits.
Apply this diff:
- # Apply sigmoid to get probabilities - probabilities = torch.sigmoid(logits) - - # Compute BCE loss - bce_loss = F.binary_cross_entropy( - probabilities, targets.float(), reduction="none" - ) + # Compute stable BCE-with-logits directly on logits + targets_f = targets.float() + weight = None + if self.class_weights is not None: + if self.class_weights.shape[-1] != logits.shape[-1]: + raise ValueError( + f"Class weights shape {self.class_weights.shape} does not match number of classes {logits.shape[-1]}" + ) + weight = self.class_weights + bce_loss = F.binary_cross_entropy_with_logits( + logits, targets_f, weight=weight, reduction="none" + )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/models/__pycache__/unified_api_server.cpython-312.pycis excluded by!**/*.pyc
📒 Files selected for processing (16)
CODE_REVIEW_IMPROVEMENTS.md(1 hunks)DEBERTA_DEPLOYMENT_README.md(1 hunks)DEBERTA_INTEGRATION_CODE_REVIEW_FIXES.md(1 hunks)configs/samo_api_config.yaml(1 hunks)configs/samo_emotion_detection_config.yaml(1 hunks)dependencies/requirements-api.txt(1 hunks)deployment/cloud-run/Dockerfile.deberta(1 hunks)deployment/cloud-run/deploy_deberta.sh(1 hunks)deployment/cloud-run/model_utils.py(7 hunks)scripts/download_model.py(1 hunks)scripts/start_api_server.py(1 hunks)scripts/testing/cloud_run_deployment_monitor.py(1 hunks)src/models/emotion_detection/samo_bert_emotion_classifier.py(1 hunks)src/models/unified_api_server.py(1 hunks)test_samo_emotion_detection_standalone.py(1 hunks)tests/test_unified_api_server.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- DEBERTA_DEPLOYMENT_README.md
🧰 Additional context used
🧬 Code graph analysis (6)
scripts/start_api_server.py (1)
src/models/unified_api_server.py (2)
SAMOUnifiedAPIServer(134-493)run(490-493)
src/models/emotion_detection/samo_bert_emotion_classifier.py (4)
deployment/cloud-run/secure_api_server.py (4)
get(265-286)get(400-411)get(421-430)get(439-453)deployment/cloud-run/model_utils.py (1)
predict_emotions(386-463)scripts/testing/deberta_journal_inference_demo.py (1)
predict_emotions(76-143)scripts/testing/comprehensive_journal_inference_demo.py (1)
predict_emotions(81-141)
test_samo_emotion_detection_standalone.py (2)
src/models/emotion_detection/samo_bert_emotion_classifier.py (5)
create_samo_bert_emotion_classifier(427-467)count_parameters(298-300)count_frozen_parameters(302-304)predict_emotions(201-291)set_temperature(293-296)src/models/emotion_detection/emotion_labels.py (2)
get_all_emotions(269-276)get_emotion_description(243-253)
tests/test_unified_api_server.py (2)
src/models/unified_api_server.py (1)
SAMOUnifiedAPIServer(134-493)src/models/emotion_detection/samo_bert_emotion_classifier.py (1)
predict_emotions(201-291)
scripts/testing/cloud_run_deployment_monitor.py (2)
deployment/cloud-run/secure_api_server.py (6)
get(265-286)get(400-411)get(421-430)get(439-453)post(299-335)post(348-393)scripts/testing/quick_deployment_check.py (1)
main(89-111)
src/models/unified_api_server.py (1)
src/models/emotion_detection/samo_bert_emotion_classifier.py (2)
create_samo_bert_emotion_classifier(427-467)predict_emotions(201-291)
🪛 Ruff (0.12.2)
scripts/download_model.py
1-1: Shebang is present but file is not executable
(EXE001)
52-52: Consider moving this statement to an else block
(TRY300)
54-54: Do not catch blind exception: Exception
(BLE001)
scripts/start_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
38-38: Possible binding to all interfaces
(S104)
79-79: Undefined name logger
(F821)
80-80: Do not catch blind exception: Exception
(BLE001)
81-81: Undefined name logger
(F821)
82-82: Undefined name logger
(F821)
122-122: Do not catch blind exception: Exception
(BLE001)
123-123: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
src/models/emotion_detection/samo_bert_emotion_classifier.py
1-1: Shebang is present but file is not executable
(EXE001)
204-204: PEP 484 prohibits implicit Optional
Convert to Optional[T]
(RUF013)
349-352: Avoid specifying long messages outside the exception class
(TRY003)
410-412: Avoid specifying long messages outside the exception class
(TRY003)
test_samo_emotion_detection_standalone.py
1-1: Shebang is present but file is not executable
(EXE001)
112-112: Use explicit conversion flag
Replace with conversion flag
(RUF010)
117-117: Do not catch blind exception: Exception
(BLE001)
236-236: Do not catch blind exception: Exception
(BLE001)
288-288: Do not catch blind exception: Exception
(BLE001)
tests/test_unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
deployment/cloud-run/model_utils.py
75-75: Unused method argument: kwargs
(ARG002)
131-131: Avoid specifying long messages outside the exception class
(TRY003)
174-174: Consider moving this statement to an else block
(TRY300)
177-177: Redundant exception object included in logging.exception call
(TRY401)
287-287: Do not catch blind exception: Exception
(BLE001)
292-292: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
314-314: Local variable max_length is assigned to but never used
Remove assignment to unused variable max_length
(F841)
326-326: Do not catch blind exception: Exception
(BLE001)
331-331: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
365-365: Do not catch blind exception: Exception
(BLE001)
533-533: Redundant exception object included in logging.exception call
(TRY401)
scripts/testing/cloud_run_deployment_monitor.py
1-1: Shebang is present but file is not executable
(EXE001)
33-33: subprocess call: check for execution of untrusted input
(S603)
40-40: Consider moving this statement to an else block
(TRY300)
45-45: Do not catch blind exception: Exception
(BLE001)
60-60: Consider moving this statement to an else block
(TRY300)
68-68: Do not catch blind exception: Exception
(BLE001)
91-91: Consider moving this statement to an else block
(TRY300)
93-93: Do not catch blind exception: Exception
(BLE001)
156-156: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF001)
168-168: Undefined name os
(F821)
172-172: subprocess call: check for execution of untrusted input
(S603)
181-181: Do not catch blind exception: Exception
(BLE001)
src/models/unified_api_server.py
1-1: Shebang is present but file is not executable
(EXE001)
176-176: Do not catch blind exception: Exception
(BLE001)
177-177: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
185-185: Do not catch blind exception: Exception
(BLE001)
186-186: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
195-195: Do not catch blind exception: Exception
(BLE001)
196-196: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
232-232: Do not catch blind exception: Exception
(BLE001)
233-233: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
234-236: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
235-235: Use explicit conversion flag
Replace with conversion flag
(RUF010)
241-241: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
278-278: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
304-304: Do not catch blind exception: Exception
(BLE001)
305-305: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
306-308: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
307-307: Use explicit conversion flag
Replace with conversion flag
(RUF010)
343-343: Do not catch blind exception: Exception
(BLE001)
344-344: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
345-347: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
346-346: Use explicit conversion flag
Replace with conversion flag
(RUF010)
352-352: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
365-367: Abstract raise to an inner function
(TRY301)
370-370: Undefined name uuid
(F821)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
372-372: Probable insecure usage of temporary file or directory: "/tmp/"
(S108)
461-461: Do not catch blind exception: Exception
(BLE001)
462-462: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
463-465: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
464-464: Use explicit conversion flag
Replace with conversion flag
(RUF010)
490-490: Possible binding to all interfaces
(S104)
🪛 Gitleaks (8.27.2)
CODE_REVIEW_IMPROVEMENTS.md
[high] 75-77: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
DEBERTA_INTEGRATION_CODE_REVIEW_FIXES.md
[high] 130-135: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🪛 markdownlint-cli2 (0.17.2)
DEBERTA_INTEGRATION_CODE_REVIEW_FIXES.md
81-81: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 YAMLlint (1.37.1)
configs/samo_emotion_detection_config.yaml
[error] 13-13: trailing spaces
(trailing-spaces)
[error] 16-16: trailing spaces
(trailing-spaces)
[error] 19-19: trailing spaces
(trailing-spaces)
[error] 28-28: trailing spaces
(trailing-spaces)
[error] 31-31: trailing spaces
(trailing-spaces)
[error] 34-34: trailing spaces
(trailing-spaces)
[error] 43-43: trailing spaces
(trailing-spaces)
[error] 47-47: trailing spaces
(trailing-spaces)
[error] 51-51: trailing spaces
(trailing-spaces)
[error] 55-55: trailing spaces
(trailing-spaces)
[error] 66-66: trailing spaces
(trailing-spaces)
[error] 69-69: trailing spaces
(trailing-spaces)
[error] 83-83: trailing spaces
(trailing-spaces)
[error] 86-86: trailing spaces
(trailing-spaces)
[error] 96-96: trailing spaces
(trailing-spaces)
[error] 105-105: trailing spaces
(trailing-spaces)
[error] 108-108: trailing spaces
(trailing-spaces)
[error] 117-117: trailing spaces
(trailing-spaces)
[error] 121-121: trailing spaces
(trailing-spaces)
[error] 124-124: trailing spaces
(trailing-spaces)
[error] 132-132: trailing spaces
(trailing-spaces)
[error] 135-135: trailing spaces
(trailing-spaces)
[error] 138-138: trailing spaces
(trailing-spaces)
[error] 141-141: trailing spaces
(trailing-spaces)
[error] 150-150: trailing spaces
(trailing-spaces)
[error] 154-154: trailing spaces
(trailing-spaces)
[error] 163-163: trailing spaces
(trailing-spaces)
[error] 166-166: trailing spaces
(trailing-spaces)
[error] 169-169: trailing spaces
(trailing-spaces)
[error] 177-177: trailing spaces
(trailing-spaces)
[error] 180-180: trailing spaces
(trailing-spaces)
[error] 183-183: trailing spaces
(trailing-spaces)
configs/samo_api_config.yaml
[error] 86-86: no new line character at the end of file
(new-line-at-end-of-file)
🔇 Additional comments (3)
deployment/cloud-run/Dockerfile.deberta (1)
31-39: Verified: Dockerfile uses prod-optimized requirements with gunicorndeployment/docker/requirements-api-optimized.txt exists and includes gunicorn (line 23); that file contains no pytest/httpx dev deps and is the file copied by Dockerfile.deberta.
scripts/start_api_server.py (1)
117-119: No action required — run forwards workers and reload to uvicorn.
Signature includes workers/reload and the method calls uvicorn.run(..., workers=workers, reload=reload) (src/models/unified_api_server.py:490-493).deployment/cloud-run/deploy_deberta.sh (1)
37-46: LGTM — secure defaults and secrets handling addressedPROJECT_ID/ADMIN_API_KEY are required, and the key is redacted in output. Deployment flags look sane for Cloud Run warm starts.
Also applies to: 138-139, 233-246
| api: | ||
| cors_origins: | ||
| - "http://localhost:3000" | ||
| - "http://localhost:8080" | ||
| - "https://your-frontend-domain.com" | ||
| max_upload_size: 100 # MB | ||
| request_timeout: 300 # seconds | ||
| rate_limit: 100 # requests per minute per IP | ||
|
|
There was a problem hiding this comment.
Config drift: cors_origins is under api, but start script reads server.cors_origins.
CORS won’t be applied. Move or duplicate under server.
server:
host: "0.0.0.0"
port: 8000
workers: 1
reload: false
log_level: "info"
+ cors_origins:
+ - "http://localhost:3000"
+ - "http://localhost:8080"
+ - "https://your-frontend-domain.com"
-api:
- cors_origins:
- - "http://localhost:3000"
- - "http://localhost:8080"
- - "https://your-frontend-domain.com"
+api:📝 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.
| api: | |
| cors_origins: | |
| - "http://localhost:3000" | |
| - "http://localhost:8080" | |
| - "https://your-frontend-domain.com" | |
| max_upload_size: 100 # MB | |
| request_timeout: 300 # seconds | |
| rate_limit: 100 # requests per minute per IP | |
| server: | |
| host: "0.0.0.0" | |
| port: 8000 | |
| workers: 1 | |
| reload: false | |
| log_level: "info" | |
| cors_origins: | |
| - "http://localhost:3000" | |
| - "http://localhost:8080" | |
| - "https://your-frontend-domain.com" | |
| api: | |
| max_upload_size: 100 # MB | |
| request_timeout: 300 # seconds | |
| rate_limit: 100 # requests per minute per IP |
🤖 Prompt for AI Agents
configs/samo_api_config.yaml around lines 41-49: the cors_origins key is
currently nested under api but the start script reads server.cors_origins, so
CORS settings won't be applied; move or duplicate the cors_origins array under a
top-level server key (or add a server.cors_origins mapping) preserving the same
origins, and ensure any related keys (like max_upload_size, request_timeout,
rate_limit) remain under api unless intentionally moved; update the YAML to
include server.cors_origins with the same values so the runtime picks them up.
| # Core FastAPI and web framework | ||
| fastapi==0.104.1 | ||
| uvicorn[standard]==0.24.0 | ||
| pydantic==2.5.0 | ||
|
|
||
| # Machine Learning and Transformers | ||
| torch>=2.0.0 | ||
| transformers>=4.35.0 | ||
| datasets>=2.15.0 |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Pinning vs. minimums and runtime server.
If this file is used for production runs, ensure gunicorn is included; otherwise confirm Cloud Run uses the optimized requirements file instead.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find Dockerfiles/cloud build steps referencing requirements-api.txt"
rg -nP 'requirements-api\.txt|requirements-api-optimized\.txt' -g 'deployment/**' -S -C2 || true
echo
echo "Check if gunicorn is present here"
rg -n '^gunicorn' dependencies/requirements-api.txt || echo "gunicorn not listed"Length of output: 5668
Add gunicorn to production requirements or make production images use the optimized requirements
- Findings: multiple deployment artifacts install from dependencies/requirements-api.txt (deployment/local/start.sh; deployment/docker/dockerfile; deployment/docker/Dockerfile.train; deployment/docker/Dockerfile.gcp; deployment/docker/Dockerfile.emotion_arch_fixed; deployment/deploy.sh; deployment/README.md) and dependencies/requirements-api.txt does not include gunicorn.
- Action: either add gunicorn (pinned) to dependencies/requirements-api.txt for production builds, or update those Dockerfiles to use deployment/docker/requirements-api-optimized.txt (already used by deployment/docker/Dockerfile.optimized and deployment/cloud-run/Dockerfile.deberta) and verify the optimized file contains gunicorn.
🤖 Prompt for AI Agents
In dependencies/requirements-api.txt around lines 4-12, production Dockerfiles
install from this file but it lacks gunicorn; either add a pinned gunicorn entry
(e.g., gunicorn==20.1.0) to dependencies/requirements-api.txt so all production
builds install the WSGI server, or update each deployment Dockerfile/start
script to use deployment/docker/requirements-api-optimized.txt (as
Dockerfile.optimized does) and ensure that optimized requirements file contains
a pinned gunicorn; update and test the Docker builds to verify the production
images use the optimized requirements with gunicorn installed.
🚀 DeBERTa Emotion Detection API Deployment
SCOPE DECLARATION
ALLOWED: DeBERTa model integration and Cloud Run deployment
FORBIDDEN: Other model architectures, data preprocessing changes, training script modifications
FILES TOUCHED: 4 files (deployment scripts, Dockerfile, API server)
TIME ESTIMATE: 4 hours
🎯 What This PR Does
Deploys a production-ready DeBERTa emotion detection API to Google Cloud Run with 28 emotion classes and comprehensive security features.
✅ Key Features
🌐 Live API Endpoints
Service URL: https://samo-emotion-deberta-71517823771.us-central1.run.app
GET /api/health- Health check and model statusPOST /api/predict- Single text emotion predictionGET /api/emotions- List available emotion classesGET /admin/model/status- Model performance metrics🧪 Testing Results
✅ Basic Emotions: Happy (83% excitement), Sad (96% sadness), Angry (90% anger), Fearful (95% fear)
✅ Error Handling: Proper validation for empty text, invalid inputs
✅ Performance: Sub-2 second response times
✅ Security: API key authentication, rate limiting enabled
🚀 Deployment Status
LIVE AND OPERATIONAL - Ready for production use!
Summary by Sourcery
Integrate a production-grade 28-class DeBERTa emotion detection model into the API ecosystem, extend and deploy both Flask-RESTX and FastAPI servers with new endpoints and configuration, and provide Docker-based Cloud Run deployment tooling alongside comprehensive tests and documentation.
New Features:
Enhancements:
Build:
Deployment:
Documentation:
Tests:
Summary by CodeRabbit
New Features
Documentation
Bug Fixes
Chores
Tests