Habiba A. Elbehairy commited on
Commit
39efcaf
Β·
1 Parent(s): 172e5f0

Refactor model loading and feature extraction; update logging and deployment info

Browse files
Files changed (1) hide show
  1. app.py +180 -95
app.py CHANGED
@@ -1,36 +1,33 @@
1
  import os
2
- import time
3
  import logging
4
  import torch
5
  import torch.nn.functional as F
6
  from fastapi import FastAPI, HTTPException
7
  from fastapi.middleware.cors import CORSMiddleware
8
  from pydantic import BaseModel
9
- from transformers import AutoTokenizer, AutoModel
10
  from typing import List
11
  import uvicorn
12
  from datetime import datetime
13
- import requests
14
- import tempfile
15
-
16
- # Import our model and feature extraction function
17
- from model_definition import CodeSimilarityClassifier, extract_features
18
 
19
- # Set up logging
20
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
 
 
21
  logger = logging.getLogger(__name__)
22
 
23
- # System information - Updated with current values
24
- DEPLOYMENT_DATE = "2025-06-22 22:00:24"
25
  DEPLOYED_BY = "habibaelbehairy"
26
 
27
  # Get device
28
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
29
  logger.info(f"Using device: {device}")
30
 
31
- # Your Hugging Face model repository
32
- REPO_ID = "HabibaElbehairy/CodeSimilarityClassifier"
33
- HF_TOKEN = os.getenv("HF_TOKEN", None) # Optional: Set HF_TOKEN environment variable if the model is private
34
 
35
  # Initialize FastAPI app
36
  app = FastAPI(
@@ -40,7 +37,7 @@ app = FastAPI(
40
  docs_url="/",
41
  )
42
 
43
- # Add CORS middleware to allow cross-origin requests
44
  app.add_middleware(
45
  CORSMiddleware,
46
  allow_origins=["*"],
@@ -49,7 +46,7 @@ app.add_middleware(
49
  allow_headers=["*"],
50
  )
51
 
52
- # Define label to class mapping (0, 1, 2 for the new model)
53
  label_to_class = {0: "Duplicate", 1: "Redundant", 2: "Distinct"}
54
 
55
  # Define input models for API
@@ -71,91 +68,186 @@ class SimilarityInput(BaseModel):
71
  test_case_1: TestCase
72
  test_case_2: TestCase
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  # Global variables for model and tokenizer
75
- model = None
76
  tokenizer = None
 
77
 
78
- # Load model and tokenizer on startup
79
  @app.on_event("startup")
80
  async def startup_event():
81
- global model, tokenizer
 
82
  try:
83
- logger.info(f"Loading model and tokenizer from {REPO_ID}...")
84
 
85
- # Create headers with token if available
86
- headers = {}
87
- if HF_TOKEN:
88
- headers["Authorization"] = f"Bearer {HF_TOKEN}"
89
-
90
- # Load tokenizer directly from Hugging Face
 
 
 
 
 
91
  try:
92
- tokenizer = AutoTokenizer.from_pretrained(REPO_ID, use_auth_token=HF_TOKEN)
93
- logger.info("Tokenizer loaded successfully")
94
  except Exception as e:
95
- logger.error(f"Error loading tokenizer: {str(e)}")
96
  raise
97
 
98
- # Create model instance using our CodeSimilarityClassifier class
99
- model = CodeSimilarityClassifier(model_name="microsoft/codebert-base")
100
- logger.info("Model created successfully")
101
 
102
- # Download the model weights file directly
103
- try:
104
- model_url = f"https://huggingface.co/{REPO_ID}/resolve/main/pytorch_model.bin"
105
- logger.info(f"Downloading model weights from: {model_url}")
106
-
107
- with tempfile.NamedTemporaryFile(delete=False) as tmp:
108
- # Make the request to download the file
109
- response = requests.get(model_url, headers=headers, stream=True)
110
- if not response.ok:
111
- logger.error(f"Failed to download model: {response.status_code} - {response.text}")
112
- raise Exception(f"Failed to download model: {response.status_code}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- # Save the file to a temporary location
115
- for chunk in response.iter_content(chunk_size=8192):
116
- if chunk:
117
- tmp.write(chunk)
118
 
119
- tmp_name = tmp.name
120
-
121
- # Load the model weights
122
- logger.info(f"Loading model weights from temporary file")
123
- state_dict = torch.load(tmp_name, map_location=device)
124
- model.load_state_dict(state_dict)
125
-
126
- # Clean up the temporary file
127
- os.unlink(tmp_name)
128
- logger.info("Model weights loaded successfully")
129
- except Exception as e:
130
- logger.error(f"Error downloading model weights: {str(e)}")
131
- raise
132
 
133
  # Move model to device and set to evaluation mode
134
  model.to(device)
135
  model.eval()
 
 
136
 
137
- logger.info("Model and tokenizer loaded successfully!")
138
  except Exception as e:
139
- logger.error(f"Error loading model: {str(e)}")
140
  import traceback
141
  logger.error(traceback.format_exc())
142
- model = None
143
- tokenizer = None
 
 
 
144
 
145
- @app.get("/health", tags=["Health"])
146
  async def health_check():
147
  """Health check endpoint that also returns deployment information"""
148
- if model is None or tokenizer is None:
149
- return {
150
- "status": "error",
151
- "message": "Model or tokenizer not loaded",
152
- "deployment_date": DEPLOYMENT_DATE,
153
- "deployed_by": DEPLOYED_BY
154
- }
155
 
156
  return {
157
- "status": "ok",
158
- "model": REPO_ID,
 
159
  "device": str(device),
160
  "deployment_date": DEPLOYMENT_DATE,
161
  "deployed_by": DEPLOYED_BY,
@@ -166,11 +258,8 @@ async def health_check():
166
  async def predict(data: SimilarityInput):
167
  """
168
  Predict similarity class between two test cases for a given source class.
169
-
170
- Input schema follows the specified format with source_code, test_case_1, and test_case_2.
171
- Uses feature extraction and the CodeSimilarityClassifier model for classification.
172
  """
173
- if model is None:
174
  raise HTTPException(status_code=500, detail="Model not loaded correctly")
175
 
176
  try:
@@ -213,7 +302,7 @@ async def predict(data: SimilarityInput):
213
 
214
  # THIS IS WHERE THE MODEL IS CALLED
215
  with torch.no_grad():
216
- # Our CodeSimilarityClassifier model returns logits directly
217
  logits = model(
218
  input_ids=inputs["input_ids"],
219
  attention_mask=inputs["attention_mask"]
@@ -238,8 +327,7 @@ async def predict(data: SimilarityInput):
238
  "score": api_score,
239
  "classification": classification,
240
  },
241
- "probabilities": probs,
242
- "extracted_features": features # Include extracted features for transparency
243
  }
244
 
245
  except Exception as e:
@@ -249,8 +337,17 @@ async def predict(data: SimilarityInput):
249
  logger.error(error_trace)
250
  raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
251
 
252
- # Example endpoint
253
- @app.get("/example", response_model=SimilarityInput, tags=["Examples"])
 
 
 
 
 
 
 
 
 
254
  async def get_example():
255
  """Get an example input to test the API"""
256
  return SimilarityInput(
@@ -277,17 +374,5 @@ async def get_example():
277
  )
278
  )
279
 
280
- @app.get("/", tags=["Root"])
281
- async def root():
282
- """
283
- Root endpoint that provides API information and links to documentation.
284
- """
285
- return {
286
- "message": "Test Similarity Analyzer API",
287
- "documentation": "/docs",
288
- "deployment_date": DEPLOYMENT_DATE,
289
- "deployed_by": DEPLOYED_BY
290
- }
291
-
292
  if __name__ == "__main__":
293
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
 
1
  import os
 
2
  import logging
3
  import torch
4
  import torch.nn.functional as F
5
  from fastapi import FastAPI, HTTPException
6
  from fastapi.middleware.cors import CORSMiddleware
7
  from pydantic import BaseModel
 
8
  from typing import List
9
  import uvicorn
10
  from datetime import datetime
11
+ from transformers import AutoTokenizer, AutoModel
 
 
 
 
12
 
13
+ # Set up logging with more details
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - %(message)s',
17
+ handlers=[logging.StreamHandler()]
18
+ )
19
  logger = logging.getLogger(__name__)
20
 
21
+ # System information
22
+ DEPLOYMENT_DATE = "2025-06-22 22:05:09"
23
  DEPLOYED_BY = "habibaelbehairy"
24
 
25
  # Get device
26
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
27
  logger.info(f"Using device: {device}")
28
 
29
+ # Your Hugging Face model repository - this is the key configuration
30
+ MODEL_NAME = "HabibaElbehairy/CodeSimilarityClassifier"
 
31
 
32
  # Initialize FastAPI app
33
  app = FastAPI(
 
37
  docs_url="/",
38
  )
39
 
40
+ # Add CORS middleware
41
  app.add_middleware(
42
  CORSMiddleware,
43
  allow_origins=["*"],
 
46
  allow_headers=["*"],
47
  )
48
 
49
+ # Define label to class mapping
50
  label_to_class = {0: "Duplicate", 1: "Redundant", 2: "Distinct"}
51
 
52
  # Define input models for API
 
68
  test_case_1: TestCase
69
  test_case_2: TestCase
70
 
71
+ # Define the model class
72
+ class CodeSimilarityClassifier(torch.nn.Module):
73
+ def __init__(self, model_name="microsoft/codebert-base", num_labels=3):
74
+ super().__init__()
75
+ self.encoder = AutoModel.from_pretrained(model_name)
76
+ self.dropout = torch.nn.Dropout(0.1)
77
+
78
+ # Create a more powerful classification head
79
+ hidden_size = self.encoder.config.hidden_size
80
+
81
+ self.classifier = torch.nn.Sequential(
82
+ torch.nn.Linear(hidden_size, hidden_size),
83
+ torch.nn.LayerNorm(hidden_size),
84
+ torch.nn.GELU(),
85
+ torch.nn.Dropout(0.1),
86
+ torch.nn.Linear(hidden_size, 512),
87
+ torch.nn.LayerNorm(512),
88
+ torch.nn.GELU(),
89
+ torch.nn.Dropout(0.1),
90
+ torch.nn.Linear(512, num_labels)
91
+ )
92
+
93
+ def forward(self, input_ids, attention_mask):
94
+ outputs = self.encoder(
95
+ input_ids=input_ids,
96
+ attention_mask=attention_mask,
97
+ return_dict=True
98
+ )
99
+
100
+ pooled_output = outputs.pooler_output
101
+ logits = self.classifier(pooled_output)
102
+
103
+ return logits
104
+
105
+ # Function to extract features from code
106
+ def extract_features(source_code, test_code_1, test_code_2):
107
+ """Extract specific features to help the model identify similarities"""
108
+ import re
109
+
110
+ # Extract test fixtures
111
+ fixture1 = re.search(r'TEST(?:_F)?\s*\(\s*(\w+)', test_code_1)
112
+ fixture1 = fixture1.group(1) if fixture1 else ""
113
+
114
+ fixture2 = re.search(r'TEST(?:_F)?\s*\(\s*(\w+)', test_code_2)
115
+ fixture2 = fixture2.group(1) if fixture2 else ""
116
+
117
+ # Extract test names
118
+ name1 = re.search(r'TEST(?:_F)?\s*\(\s*\w+\s*,\s*(\w+)', test_code_1)
119
+ name1 = name1.group(1) if name1 else ""
120
+
121
+ name2 = re.search(r'TEST(?:_F)?\s*\(\s*\w+\s*,\s*(\w+)', test_code_2)
122
+ name2 = name2.group(1) if name2 else ""
123
+
124
+ # Extract assertions
125
+ assertions1 = re.findall(r'(EXPECT_|ASSERT_)(\w+)', test_code_1)
126
+ assertions2 = re.findall(r'(EXPECT_|ASSERT_)(\w+)', test_code_2)
127
+
128
+ # Extract function/method calls
129
+ calls1 = re.findall(r'(\w+)\s*\(', test_code_1)
130
+ calls2 = re.findall(r'(\w+)\s*\(', test_code_2)
131
+
132
+ # Create explicit feature section
133
+ same_fixture = "SAME_FIXTURE" if fixture1 == fixture2 else "DIFFERENT_FIXTURE"
134
+ common_assertions = set([a[0] + a[1] for a in assertions1]).intersection(set([a[0] + a[1] for a in assertions2]))
135
+ common_calls = set(calls1).intersection(set(calls2))
136
+
137
+ features = (
138
+ f"METADATA: {same_fixture} | "
139
+ f"FIXTURE1: {fixture1} | FIXTURE2: {fixture2} | "
140
+ f"NAME1: {name1} | NAME2: {name2} | "
141
+ f"COMMON_ASSERTIONS: {len(common_assertions)} | "
142
+ f"COMMON_CALLS: {len(common_calls)} | "
143
+ f"ASSERTION_RATIO: {len(common_assertions)/(len(assertions1) + len(assertions2)) if assertions1 and assertions2 else 0}"
144
+ )
145
+
146
+ return features
147
+
148
  # Global variables for model and tokenizer
 
149
  tokenizer = None
150
+ model = None
151
 
152
+ # Load model and tokenizer on startup - COMPLETELY REWORKED
153
  @app.on_event("startup")
154
  async def startup_event():
155
+ global tokenizer, model
156
+
157
  try:
158
+ logger.info("=== Starting model loading process ===")
159
 
160
+ # Step 1: Load the tokenizer first (simpler)
161
+ logger.info(f"Loading tokenizer from {MODEL_NAME}...")
162
+ try:
163
+ tokenizer = AutoTokenizer.from_pretrained("microsoft/codebert-base")
164
+ logger.info("βœ… Base tokenizer loaded successfully")
165
+ except Exception as e:
166
+ logger.error(f"❌ Failed to load tokenizer: {str(e)}")
167
+ raise
168
+
169
+ # Step 2: Create model with base architecture
170
+ logger.info("Creating model architecture...")
171
  try:
172
+ model = CodeSimilarityClassifier(model_name="microsoft/codebert-base")
173
+ logger.info("βœ… Model architecture created successfully")
174
  except Exception as e:
175
+ logger.error(f"❌ Failed to create model architecture: {str(e)}")
176
  raise
177
 
178
+ # Step 3: Load weights from locally saved file if available
179
+ logger.info("Looking for model weights...")
 
180
 
181
+ # Try several possible locations
182
+ possible_paths = [
183
+ "pytorch_model.bin",
184
+ "/app/pytorch_model.bin",
185
+ "model/pytorch_model.bin",
186
+ os.path.join(os.getcwd(), "pytorch_model.bin")
187
+ ]
188
+
189
+ model_loaded = False
190
+ for path in possible_paths:
191
+ if os.path.exists(path):
192
+ logger.info(f"Found model weights at {path}")
193
+ try:
194
+ state_dict = torch.load(path, map_location=device)
195
+ model.load_state_dict(state_dict)
196
+ logger.info(f"βœ… Successfully loaded weights from {path}")
197
+ model_loaded = True
198
+ break
199
+ except Exception as e:
200
+ logger.warning(f"⚠️ Failed to load weights from {path}: {str(e)}")
201
+
202
+ # If local loading fails, try fetching from HuggingFace
203
+ if not model_loaded:
204
+ logger.info("Attempting to fetch model directly from Hugging Face...")
205
+ try:
206
+ # Pull model from Hugging Face
207
+ temp_model = AutoModel.from_pretrained(MODEL_NAME)
208
+ logger.info("βœ… Base model loaded from Hugging Face")
209
 
210
+ # Initialize our custom model with the base model's weights
211
+ model = CodeSimilarityClassifier(model_name="microsoft/codebert-base")
 
 
212
 
213
+ # Only copy the encoder part (which is what we loaded from HF)
214
+ model.encoder = temp_model
215
+ logger.info("βœ… Successfully initialized model with base encoder")
216
+ model_loaded = True
217
+ except Exception as e:
218
+ logger.error(f"❌ Failed to fetch from Hugging Face: {str(e)}")
219
+
220
+ # Final check
221
+ if not model_loaded:
222
+ logger.error("❌ CRITICAL: Could not load model weights from any source")
223
+ raise RuntimeError("Failed to load model weights")
 
 
224
 
225
  # Move model to device and set to evaluation mode
226
  model.to(device)
227
  model.eval()
228
+ logger.info(f"βœ… Model moved to {device} and set to evaluation mode")
229
+ logger.info("=== Model loading process complete ===")
230
 
 
231
  except Exception as e:
232
+ logger.error(f"❌ CRITICAL ERROR in startup: {str(e)}")
233
  import traceback
234
  logger.error(traceback.format_exc())
235
+ if model is None:
236
+ logger.error("❌ Model object is None")
237
+ if tokenizer is None:
238
+ logger.error("❌ Tokenizer object is None")
239
+ raise
240
 
241
+ @app.get("/health")
242
  async def health_check():
243
  """Health check endpoint that also returns deployment information"""
244
+ status = "ok" if (model is not None and tokenizer is not None) else "error"
245
+ message = "Model and tokenizer loaded correctly" if status == "ok" else "Model or tokenizer not loaded"
 
 
 
 
 
246
 
247
  return {
248
+ "status": status,
249
+ "message": message,
250
+ "model": MODEL_NAME,
251
  "device": str(device),
252
  "deployment_date": DEPLOYMENT_DATE,
253
  "deployed_by": DEPLOYED_BY,
 
258
  async def predict(data: SimilarityInput):
259
  """
260
  Predict similarity class between two test cases for a given source class.
 
 
 
261
  """
262
+ if model is None or tokenizer is None:
263
  raise HTTPException(status_code=500, detail="Model not loaded correctly")
264
 
265
  try:
 
302
 
303
  # THIS IS WHERE THE MODEL IS CALLED
304
  with torch.no_grad():
305
+ # Our custom model
306
  logits = model(
307
  input_ids=inputs["input_ids"],
308
  attention_mask=inputs["attention_mask"]
 
327
  "score": api_score,
328
  "classification": classification,
329
  },
330
+ "probabilities": probs
 
331
  }
332
 
333
  except Exception as e:
 
337
  logger.error(error_trace)
338
  raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
339
 
340
+ # Root and example routes remain the same
341
+ @app.get("/")
342
+ async def root():
343
+ return {
344
+ "message": "Test Similarity Analyzer API",
345
+ "documentation": "/docs",
346
+ "deployment_date": DEPLOYMENT_DATE,
347
+ "deployed_by": DEPLOYED_BY
348
+ }
349
+
350
+ @app.get("/example", response_model=SimilarityInput)
351
  async def get_example():
352
  """Get an example input to test the API"""
353
  return SimilarityInput(
 
374
  )
375
  )
376
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  if __name__ == "__main__":
378
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)