shiva9876 commited on
Commit
59da258
·
verified ·
1 Parent(s): 8c06638

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +337 -0
app.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import uuid
4
+ import requests
5
+ import re
6
+ from fastapi import FastAPI, HTTPException, Request
7
+ from pydantic import BaseModel, Field
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from functools import lru_cache
10
+ from typing import Optional, Dict, Any, List
11
+ from dotenv import load_dotenv
12
+
13
+ # Load .env automatically from the project directory
14
+ load_dotenv()
15
+
16
+ # Read API key from environment
17
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
18
+
19
+ # Hardcoded configuration
20
+ GROQ_MODEL = "moonshotai/kimi-k2-instruct-0905" # Default Groq model
21
+ MAX_TOKENS = 2000
22
+ TEMPERATURE = 0.5
23
+
24
+ # Debugging: Check if API key is loaded
25
+ if not GROQ_API_KEY:
26
+ print("❌ GROQ_API_KEY is not set. Check your .env file or environment variables.")
27
+ else:
28
+ print(f"✅ GROQ_API_KEY Loaded: {GROQ_API_KEY[:10]}******") # Masked for security
29
+
30
+ print(f"📦 GROQ_MODEL Loaded: {GROQ_MODEL}")
31
+ print(f"⚙️ Using parameters: MAX_TOKENS={MAX_TOKENS}, TEMPERATURE={TEMPERATURE}")
32
+
33
+ # Initialize FastAPI app
34
+ app = FastAPI(
35
+ title="Code Generation API with Groq",
36
+ description="API for generating code and explanations using Groq's LLM models",
37
+ version="1.0.0"
38
+ )
39
+
40
+ # Enable CORS for frontend communication
41
+ app.add_middleware(
42
+ CORSMiddleware,
43
+ allow_origins=["*"], # Update this with frontend domain in production
44
+ allow_credentials=True,
45
+ allow_methods=["*"],
46
+ allow_headers=["*"],
47
+ )
48
+
49
+ # In-memory conversation history (use Redis/DB for production)
50
+ conversation_history: Dict[str, List[Dict[str, str]]] = {}
51
+
52
+
53
+ # Define request formats
54
+ class PromptRequest(BaseModel):
55
+ prompt: str = Field(..., description="The user's prompt or question")
56
+ session_id: Optional[str] = Field(None, description="Session ID for conversation history")
57
+ response_type: Optional[str] = Field("both", description="Type of response: 'code', 'explanation', or 'both'")
58
+
59
+
60
+ class HistoryRequest(BaseModel):
61
+ session_id: str = Field(..., description="Session ID to retrieve or clear history")
62
+
63
+
64
+ def classify_message(message: str) -> str:
65
+ """Classify whether the message is conversational or code-related."""
66
+
67
+ # Convert message to lowercase for comparison
68
+ message_lower = message.lower().strip()
69
+
70
+ # List of common conversational greetings and phrases
71
+ conversational_phrases = [
72
+ "hi", "hello", "hey", "hi there", "hello there", "hey there",
73
+ "how are you", "good morning", "good afternoon", "good evening",
74
+ "what's up", "how's it going", "nice to meet you", "bye", "goodbye",
75
+ "thank you", "thanks", "ok", "okay", "yes", "no", "maybe",
76
+ "help", "who are you", "what can you do", "what are you",
77
+ "tell me about yourself"
78
+ ]
79
+
80
+ # Check if the message is a question or conversation
81
+ if any(message_lower.startswith(phrase) for phrase in conversational_phrases) or \
82
+ any(phrase in message_lower for phrase in conversational_phrases[:10]) or \
83
+ (message_lower.endswith("?") and len(message_lower.split()) <= 8):
84
+ return "conversation"
85
+
86
+ # Check for code-related keywords
87
+ code_keywords = ["code", "function", "script", "program", "algorithm", "implement",
88
+ "write", "create", "python", "javascript", "java", "c++"]
89
+
90
+ if any(keyword in message_lower for keyword in code_keywords):
91
+ return "code"
92
+
93
+ # If in doubt, treat as conversation
94
+ return "conversation"
95
+
96
+
97
+ # API call function with retry and improved error handling
98
+ def generate_response_groq(messages: List[Dict[str, str]]) -> str:
99
+ """Sends messages to Groq API and returns the generated response."""
100
+ if not GROQ_API_KEY:
101
+ raise HTTPException(status_code=500, detail="GROQ_API_KEY is missing.")
102
+
103
+ url = "https://api.groq.com/openai/v1/chat/completions"
104
+ headers = {
105
+ "Authorization": f"Bearer {GROQ_API_KEY}",
106
+ "Content-Type": "application/json"
107
+ }
108
+ payload = {
109
+ "model": GROQ_MODEL,
110
+ "messages": messages,
111
+ "temperature": TEMPERATURE,
112
+ "max_tokens": MAX_TOKENS,
113
+ }
114
+
115
+ for attempt in range(3): # Retry logic
116
+ try:
117
+ print(f"🔄 Attempt {attempt + 1} - Sending request to Groq API")
118
+ response = requests.post(url, headers=headers, json=payload, timeout=60)
119
+ print(f"📊 Status Code: {response.status_code}")
120
+
121
+ if response.status_code == 200:
122
+ result = response.json()
123
+ if "choices" in result and len(result["choices"]) > 0:
124
+ generated_text = result["choices"][0]["message"]["content"]
125
+ return generated_text
126
+ return "No response generated"
127
+
128
+ elif response.status_code == 401: # Unauthorized (Invalid API key)
129
+ print("❌ Authentication error: Invalid API Key")
130
+ raise HTTPException(status_code=401, detail="Invalid API Key. Check your GROQ_API_KEY.")
131
+
132
+ elif response.status_code == 429: # Rate limit error
133
+ print("⚠️ Rate limited, retrying...")
134
+ time.sleep(2 ** attempt) # Exponential backoff
135
+ continue
136
+
137
+ elif response.status_code == 503: # Service unavailable
138
+ print("⚠️ Service unavailable, retrying...")
139
+ time.sleep(2 ** attempt)
140
+ continue
141
+
142
+ else:
143
+ error_detail = "Unknown error"
144
+ try:
145
+ error_data = response.json()
146
+ error_detail = error_data.get("error", {}).get("message", str(error_data))
147
+ except:
148
+ error_detail = response.text
149
+
150
+ print(f"❌ API Error: {error_detail}")
151
+ if attempt == 2: # Last attempt
152
+ raise HTTPException(status_code=response.status_code,
153
+ detail=f"Groq API Error: {error_detail}")
154
+
155
+ except requests.exceptions.Timeout:
156
+ print("⚠️ Request timed out, retrying...")
157
+ if attempt == 2: # Last attempt
158
+ raise HTTPException(status_code=504, detail="Request timed out")
159
+
160
+ except requests.exceptions.ConnectionError:
161
+ print("⚠️ Connection error, retrying...")
162
+ if attempt == 2: # Last attempt
163
+ raise HTTPException(status_code=503, detail="Could not connect to Groq API")
164
+
165
+ except Exception as e:
166
+ print(f"❌ Unexpected error: {str(e)}")
167
+ if attempt == 2: # Last attempt
168
+ raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
169
+
170
+ # Wait before retry (except on last attempt)
171
+ if attempt < 2:
172
+ time.sleep(2 ** attempt)
173
+
174
+ raise HTTPException(status_code=500, detail="Failed to get response after multiple attempts")
175
+
176
+
177
+ # Helper function to process and format the model's response
178
+ def process_response(raw_response: str, response_type: str) -> Dict[str, Any]:
179
+ """Process and format the model's response based on the requested type."""
180
+
181
+ # For conversational responses, don't try to extract code
182
+ if response_type == "conversation":
183
+ return {"response": raw_response}
184
+
185
+ elif response_type == "code":
186
+ # Extract code blocks with regex
187
+ code_match = re.search(r"```(?:python|javascript|java|cpp|c\+\+)?\n(.*?)\n```", raw_response, re.DOTALL)
188
+ if code_match:
189
+ return {"generated_code": code_match.group(1).strip()}
190
+ # If no code block found, return the whole response as code
191
+ return {"generated_code": raw_response}
192
+
193
+ elif response_type == "explanation":
194
+ # Remove code blocks
195
+ explanation = re.sub(r"```(?:\w+)?\n.*?\n```", "", raw_response, flags=re.DOTALL).strip()
196
+ return {"explanation": explanation}
197
+
198
+ else: # "both"
199
+ code = None
200
+ explanation = raw_response
201
+
202
+ # Extract code blocks
203
+ code_match = re.search(r"```(?:python|javascript|java|cpp|c\+\+)?\n(.*?)\n```", raw_response, re.DOTALL)
204
+ if code_match:
205
+ code = code_match.group(1).strip()
206
+ # Remove code blocks from explanation
207
+ explanation = re.sub(r"```(?:\w+)?\n.*?\n```", "", raw_response, flags=re.DOTALL).strip()
208
+
209
+ return {
210
+ "response": raw_response,
211
+ "generated_code": code,
212
+ "explanation": explanation
213
+ }
214
+
215
+
216
+ # API route for generating responses
217
+ @app.post("/generate/")
218
+ async def generate_response(request: PromptRequest):
219
+ """Handles incoming user requests, maintains session history, and calls Groq model."""
220
+ try:
221
+ session_id = request.session_id or str(uuid.uuid4())
222
+
223
+ if session_id not in conversation_history:
224
+ conversation_history[session_id] = []
225
+
226
+ # Classify the message type first
227
+ message_type = classify_message(request.prompt)
228
+
229
+ # Build messages array for Groq API (OpenAI format)
230
+ messages = []
231
+
232
+ # Add system message based on response type
233
+ if message_type == "conversation":
234
+ system_prompt = "You are a helpful and friendly AI assistant. Engage in natural conversation and answer questions clearly."
235
+ else:
236
+ if request.response_type == "code":
237
+ system_prompt = "You are an expert programmer. Provide clean, efficient code solutions. Always wrap code in markdown code blocks with the appropriate language tag."
238
+ elif request.response_type == "explanation":
239
+ system_prompt = "You are a programming tutor. Explain programming concepts clearly without providing code. Focus on the approach and logic."
240
+ else: # both
241
+ system_prompt = "You are an expert programmer and teacher. Provide clear explanations followed by well-commented code examples. Always wrap code in markdown code blocks."
242
+
243
+ messages.append({"role": "system", "content": system_prompt})
244
+
245
+ # Add conversation history (last 6 messages to keep context manageable)
246
+ if conversation_history[session_id]:
247
+ for msg in conversation_history[session_id][-6:]:
248
+ messages.append(msg)
249
+
250
+ # Add current user message
251
+ messages.append({"role": "user", "content": request.prompt})
252
+
253
+ # Get response from Groq model
254
+ print(f"📤 Sending {len(messages)} messages to Groq...")
255
+ generated_response = generate_response_groq(messages)
256
+ print(f"✅ Received response of length: {len(generated_response)}")
257
+
258
+ # Store conversation history in OpenAI message format
259
+ conversation_history[session_id].append({"role": "user", "content": request.prompt})
260
+ conversation_history[session_id].append({"role": "assistant", "content": generated_response})
261
+
262
+ # Limit history size to prevent memory issues (keep last 20 messages = 10 exchanges)
263
+ if len(conversation_history[session_id]) > 20:
264
+ conversation_history[session_id] = conversation_history[session_id][-20:]
265
+
266
+ # For conversational messages, return directly without code/explanation processing
267
+ if message_type == "conversation":
268
+ response_data = {
269
+ "response": generated_response,
270
+ "message_type": "conversation"
271
+ }
272
+ else:
273
+ # Handle response type and build response data for code-related messages
274
+ response_data = process_response(generated_response, request.response_type)
275
+ response_data["message_type"] = "code"
276
+
277
+ response_data["session_id"] = session_id
278
+ return response_data
279
+
280
+ except HTTPException as e:
281
+ # Re-raise HTTP exceptions to maintain status codes
282
+ raise
283
+ except Exception as e:
284
+ print(f"❌ Unexpected error in generate_response: {str(e)}")
285
+ raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
286
+
287
+
288
+ # API route for clearing conversation history
289
+ @app.post("/clear_history/")
290
+ async def clear_history(request: HistoryRequest):
291
+ """Clears conversation history for a given session."""
292
+ if request.session_id in conversation_history:
293
+ conversation_history[request.session_id] = []
294
+ return {"status": "success", "message": "Conversation history cleared"}
295
+ return {"status": "not_found", "message": "Session ID not found"}
296
+
297
+
298
+ # API route for getting conversation history
299
+ @app.post("/get_history/")
300
+ async def get_history(request: HistoryRequest):
301
+ """Gets conversation history for a given session."""
302
+ if request.session_id in conversation_history:
303
+ return {
304
+ "status": "success",
305
+ "history": conversation_history[request.session_id]
306
+ }
307
+ return {"status": "not_found", "message": "Session ID not found"}
308
+
309
+
310
+ # Health check endpoint
311
+ @app.get("/")
312
+ @app.get("/health")
313
+ async def health_check():
314
+ """Health check endpoint to verify the API is running."""
315
+ return {
316
+ "status": "ok",
317
+ "service": "Groq Code Generation API",
318
+ "model": GROQ_MODEL,
319
+ "version": "1.0.0"
320
+ }
321
+
322
+
323
+ # Request logging middleware for debugging
324
+ @app.middleware("http")
325
+ async def log_requests(request: Request, call_next):
326
+ """Log all incoming requests for debugging."""
327
+ start_time = time.time()
328
+ response = await call_next(request)
329
+ process_time = time.time() - start_time
330
+ print(f"📝 {request.method} {request.url.path} → Status: {response.status_code} ({process_time:.2f}s)")
331
+ return response
332
+
333
+
334
+ if __name__ == "__main__":
335
+ import uvicorn
336
+ port = int(os.getenv("PORT", "7860")) # Hugging Face Spaces uses port 7860
337
+ uvicorn.run(app, host="0.0.0.0", port=port)