n0v33n commited on
Commit
ff8f2b3
Β·
1 Parent(s): 61668dd

Initial Gradio setup

Browse files
Files changed (4) hide show
  1. .gitignore +1 -0
  2. Dockerfile +28 -0
  3. app.py +327 -0
  4. requirements.txt +9 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .env
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python image
2
+ FROM python:3.12-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1
7
+
8
+ # Set working directory
9
+ WORKDIR /app
10
+
11
+ # System dependencies
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential \
14
+ curl \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Install Python dependencies
18
+ COPY requirements.txt .
19
+ RUN pip install --upgrade pip && pip install -r requirements.txt
20
+
21
+ # Copy source code
22
+ COPY . .
23
+
24
+ # Expose port
25
+ EXPOSE 8000
26
+
27
+ # Run the app
28
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import re
4
+ import pandas as pd
5
+ import random
6
+ import warnings
7
+ from fastapi import FastAPI, HTTPException
8
+ from pydantic import BaseModel
9
+ from dotenv import load_dotenv
10
+ from langchain_tavily import TavilySearch
11
+ import google.generativeai as genai
12
+ import gdown
13
+
14
+ warnings.filterwarnings("ignore")
15
+
16
+ load_dotenv()
17
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
18
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
19
+
20
+ user_sessions = {}
21
+ if not GOOGLE_API_KEY:
22
+ raise ValueError("GOOGLE_API_KEY environment variable is required.")
23
+
24
+ genai.configure(api_key=GOOGLE_API_KEY)
25
+
26
+ # β€”β€”β€” Load or fallback LeetCode data β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
27
+ GOOGLE_SHEET_URL = "https://docs.google.com/spreadsheets/d/1KK9Mnm15hV3ALJo-quJndftWfaujJ7K2_zHMCTo5mGE/"
28
+ FILE_ID = GOOGLE_SHEET_URL.split("/d/")[1].split("/")[0]
29
+ DOWNLOAD_URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
30
+ OUTPUT_FILE = "leetcode_downloaded.xlsx"
31
+
32
+ try:
33
+ print("Downloading LeetCode data...")
34
+ gdown.download(DOWNLOAD_URL, OUTPUT_FILE, quiet=False)
35
+ LEETCODE_DATA = pd.read_excel(OUTPUT_FILE)
36
+ print(f"Loaded {len(LEETCODE_DATA)} problems")
37
+ except Exception:
38
+ print("Failed to download/read. Using fallback.")
39
+ LEETCODE_DATA = pd.DataFrame([
40
+ {"problem_no": 3151, "problem_level": "Easy", "problem_statement": "special array",
41
+ "problem_link": "https://leetcode.com/problems/special-array-i/?envType=daily-question&envId=2025-06-01"},
42
+ {"problem_no": 1752, "problem_level": "Easy", "problem_statement": "check if array is sorted and rotated",
43
+ "problem_link": "https://leetcode.com/problems/check-if-array-is-sorted-and-rotated/?envType=daily-question&envId=2025-06-01"},
44
+ {"problem_no": 3105, "problem_level": "Easy", "problem_statement": "longest strictly increasing or strictly decreasing subarray",
45
+ "problem_link": "https://leetcode.com/problems/longest-strictly-increasing-or-strictly-decreasing-subarray/?envType=daily-question&envId=2025-06-01"},
46
+ {"problem_no": 1, "problem_level": "Easy", "problem_statement": "two sum",
47
+ "problem_link": "https://leetcode.com/problems/two-sum/"},
48
+ {"problem_no": 2, "problem_level": "Medium", "problem_statement": "add two numbers",
49
+ "problem_link": "https://leetcode.com/problems/add-two-numbers/"},
50
+ {"problem_no": 3, "problem_level": "Medium", "problem_statement": "longest substring without repeating characters",
51
+ "problem_link": "https://leetcode.com/problems/longest-substring-without-repeating-characters/"},
52
+ {"problem_no": 4, "problem_level": "Hard", "problem_statement": "median of two sorted arrays",
53
+ "problem_link": "https://leetcode.com/problems/median-of-two-sorted-arrays/"},
54
+ {"problem_no": 5, "problem_level": "Medium", "problem_statement": "longest palindromic substring",
55
+ "problem_link": "https://leetcode.com/problems/longest-palindromic-substring/"}
56
+ ])
57
+
58
+ # β€”β€”β€” Helpers & Tools β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
59
+
60
+ QUESTION_TYPE_MAPPING = {
61
+ "easy": "Easy", "Easy": "Easy",
62
+ "medium": "Medium", "Medium": "Medium",
63
+ "hard": "Hard", "Hard": "Hard"
64
+ }
65
+
66
+ def preprocess_query(query: str) -> str:
67
+ for k, v in QUESTION_TYPE_MAPPING.items():
68
+ query = re.sub(rf'\b{k}\b', v, query, flags=re.IGNORECASE)
69
+ query = re.sub(r'\bproblem\s*(\d+)', r'Problem_\1', query, flags=re.IGNORECASE)
70
+ query = re.sub(r'\bquestion\s*(\d+)', r'Problem_\1', query, flags=re.IGNORECASE)
71
+ return query
72
+
73
+ def get_daily_coding_question(query: str = "") -> dict:
74
+ try:
75
+ response = "**Daily Coding Questions**\n\n"
76
+
77
+ m = re.search(r'Problem_(\d+)', query, re.IGNORECASE)
78
+ if m:
79
+ df = LEETCODE_DATA[LEETCODE_DATA['problem_no'] == int(m.group(1))]
80
+ if not df.empty:
81
+ p = df.iloc[0]
82
+ response += (
83
+ f"**Problem {p['problem_no']}**\n"
84
+ f"Level: {p['problem_level']}\n"
85
+ f"Statement: {p['problem_statement']}\n"
86
+ f"Link: {p['problem_link']}\n\n"
87
+ )
88
+ return {"status": "success", "response": response}
89
+ else:
90
+ return {"status": "error", "response": "Problem not found"}
91
+
92
+ if query.strip():
93
+ df = LEETCODE_DATA[LEETCODE_DATA['problem_statement'].str.contains(query, case=False, na=False)]
94
+ else:
95
+ df = LEETCODE_DATA
96
+
97
+ easy_questions = df[df['problem_level'] == 'Easy'].sample(min(3, len(df[df['problem_level'] == 'Easy'])))
98
+ medium_questions = df[df['problem_level'] == 'Medium'].sample(min(1, len(df[df['problem_level'] == 'Medium'])))
99
+ hard_questions = df[df['problem_level'] == 'Hard'].sample(min(1, len(df[df['problem_level'] == 'Hard'])))
100
+
101
+ response += "**Easy Questions**\n"
102
+ for i, p in enumerate(easy_questions.itertuples(), 1):
103
+ response += (
104
+ f"{i}. Problem {p.problem_no}: {p.problem_statement}\n"
105
+ f" Level: {p.problem_level}\n"
106
+ f" Link: {p.problem_link}\n\n"
107
+ )
108
+
109
+ response += "**Medium Question**\n"
110
+ for p in medium_questions.itertuples():
111
+ response += (
112
+ f"Problem {p.problem_no}: {p.problem_statement}\n"
113
+ f"Level: {p.problem_level}\n"
114
+ f"Link: {p.problem_link}\n\n"
115
+ )
116
+
117
+ response += "**Hard Question**\n"
118
+ for p in hard_questions.itertuples():
119
+ response += (
120
+ f"Problem {p.problem_no}: {p.problem_statement}\n"
121
+ f"Level: {p.problem_level}\n"
122
+ f"Link: {p.problem_link}\n"
123
+ )
124
+
125
+ return {"status": "success", "response": response}
126
+ except Exception as e:
127
+ return {"status": "error", "response": f"Error: {e}"}
128
+
129
+ def fetch_interview_questions(query: str) -> dict:
130
+ if not TAVILY_API_KEY:
131
+ return {"status": "error", "response": "Tavily API key not configured"}
132
+
133
+ if not query.strip() or query.lower() in ["a interview question", "interview question", "interview questions"]:
134
+ return {"status": "error", "response": "Please provide a specific topic for interview questions (e.g., 'Python', 'data structures', 'system design')."}
135
+
136
+ try:
137
+ tavily = TavilySearch(api_key=TAVILY_API_KEY, max_results=3)
138
+ search_query = f"{query} interview questions site:*.edu | site:*.org | site:*.gov -inurl:(signup | login)"
139
+ print(f"Executing Tavily search for: {search_query}")
140
+
141
+ # Use invoke method for TavilySearch
142
+ results = tavily.invoke(search_query)
143
+
144
+ if not results or not isinstance(results, list) or len(results) == 0:
145
+ return {"status": "success", "response": "No relevant interview questions found. Try a more specific topic or different keywords."}
146
+
147
+ resp = "**Interview Questions Search Results:**\n\n"
148
+ for i, r in enumerate(results, 1):
149
+ if isinstance(r, dict):
150
+ title = r.get('title', 'No title')
151
+ url = r.get('url', 'No URL')
152
+ content = r.get('content', '')
153
+ content = content[:200] + '…' if len(content) > 200 else content or "No preview available"
154
+ resp += f"{i}. **{title}**\n URL: {url}\n Preview: {content}\n\n"
155
+ else:
156
+ resp += f"{i}. {str(r)[:200]}{'…' if len(str(r)) > 200 else ''}\n\n"
157
+
158
+ return {"status": "success", "response": resp}
159
+
160
+ except Exception as e:
161
+ print(f"Tavily search failed: {str(e)}")
162
+ return {"status": "error", "response": f"Search failed: {str(e)}"}
163
+
164
+ def simulate_mock_interview(query: str, user_id: str = "default") -> dict:
165
+ qtype = "mixed"
166
+ if re.search(r'HR|Behavioral|hr|behavioral', query, re.IGNORECASE): qtype = "HR"
167
+ if re.search(r'Technical|System Design|technical|coding', query, re.IGNORECASE): qtype = "Technical"
168
+
169
+ if "interview question" in query.lower() and qtype == "mixed":
170
+ qtype = "HR"
171
+
172
+ if qtype == "HR":
173
+ hr_questions = [
174
+ "Tell me about yourself.",
175
+ "What is your greatest weakness?",
176
+ "Describe a challenge you overcame.",
177
+ "Why do you want to work here?",
178
+ "Where do you see yourself in 5 years?",
179
+ "Why are you leaving your current job?",
180
+ "Describe a time when you had to work with a difficult team member.",
181
+ "What are your salary expectations?",
182
+ "Tell me about a time you failed.",
183
+ "What motivates you?",
184
+ "How do you handle stress and pressure?",
185
+ "Describe your leadership style."
186
+ ]
187
+ q = random.choice(hr_questions)
188
+ return {"status": "success", "response": (
189
+ f"**Mock Interview (HR/Behavioral)**\n\n**Question:** {q}\n\nπŸ’‘ **Tips:**\n"
190
+ f"- Use the STAR method (Situation, Task, Action, Result)\n"
191
+ f"- Provide specific examples from your experience\n"
192
+ f"- Keep your answer concise but detailed\n\n**Your turn to answer!**"
193
+ )}
194
+ else:
195
+ p = LEETCODE_DATA.sample(1).iloc[0]
196
+ return {"status": "success", "response": (
197
+ f"**Mock Interview (Technical)**\n\n**Problem:** {p['problem_statement'].title()}\n"
198
+ f"**Difficulty:** {p['problem_level']}\n**Link:** {p['problem_link']}\n\nπŸ’‘ **Tips:**\n"
199
+ f"- Think out loud as you solve\n"
200
+ f"- Ask clarifying questions\n"
201
+ f"- Discuss time/space complexity\n\n**Explain your approach!**"
202
+ )}
203
+
204
+ # β€”β€”β€” The Enhanced InterviewPrepAgent β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
205
+
206
+ class InterviewPrepAgent:
207
+ def __init__(self):
208
+ self.model = genai.GenerativeModel('gemini-1.5-flash')
209
+ self.tools = {
210
+ "get_daily_coding_question": get_daily_coding_question,
211
+ "fetch_interview_questions": fetch_interview_questions,
212
+ "simulate_mock_interview": simulate_mock_interview
213
+ }
214
+ self.instruction_text = """
215
+ You are an interview preparation assistant. Analyze the user's query and determine which tool to use.
216
+
217
+ Available tools:
218
+ 1. get_daily_coding_question - For coding practice, LeetCode problems, daily questions
219
+ 2. fetch_interview_questions - For searching interview questions on specific topics
220
+ 3. simulate_mock_interview - For mock interview practice (HR/behavioral or technical)
221
+
222
+ Instructions:
223
+ - If user asks for coding questions, daily questions, LeetCode problems, practice problems -> use get_daily_coding_question
224
+ - If user asks for interview questions on specific topics, wants to search for questions -> use fetch_interview_questions
225
+ - If user asks for mock interview, interview simulation, practice interview -> use simulate_mock_interview
226
+ - For HR/behavioral questions specifically, use simulate_mock_interview
227
+
228
+ Respond ONLY with valid JSON in this exact format:
229
+ {"tool": "tool_name", "args": {"param1": "value1", "param2": "value2"}}
230
+
231
+ User Query: {query}
232
+ """
233
+
234
+ def _classify_intent(self, query: str) -> tuple[str, dict]:
235
+ query_lower = query.lower()
236
+
237
+ if any(keyword in query_lower for keyword in ["daily", "coding question", "leetcode", "practice problem", "coding practice"]):
238
+ problem_match = re.search(r'problem\s*(\d+)', query_lower)
239
+ if problem_match:
240
+ return "get_daily_coding_question", {"query": f"Problem_{problem_match.group(1)}"}
241
+
242
+ if "easy" in query_lower:
243
+ return "get_daily_coding_question", {"query": "Easy"}
244
+ elif "medium" in query_lower:
245
+ return "get_daily_coding_question", {"query": "Medium"}
246
+ elif "hard" in query_lower:
247
+ return "get_daily_coding_question", {"query": "Hard"}
248
+
249
+ return "get_daily_coding_question", {"query": ""}
250
+
251
+ if any(keyword in query_lower for keyword in ["mock interview", "practice interview", "interview simulation"]) or \
252
+ ("give" in query_lower and "interview question" in query_lower):
253
+ return "simulate_mock_interview", {"query": query, "user_id": "default"}
254
+
255
+ if "interview question" in query_lower and any(word in query_lower for word in ["technical", "hr", "behavioral"]):
256
+ return "simulate_mock_interview", {"query": query, "user_id": "default"}
257
+
258
+ if any(keyword in query_lower for keyword in ["search interview questions", "find interview questions", "interview prep resources"]) or \
259
+ (query_lower.startswith("fetch_interview_questions") and "give" not in query_lower):
260
+ return "fetch_interview_questions", {"query": query}
261
+
262
+ try:
263
+ prompt = self.instruction_text.format(query=query)
264
+ response = self.model.generate_content(prompt)
265
+ result = json.loads(response.text.strip())
266
+ tool_name = result.get("tool")
267
+ args = result.get("args", {})
268
+ return tool_name, args
269
+ except Exception as e:
270
+ print(f"LLM classification failed: {e}")
271
+ return "get_daily_coding_question", {"query": ""}
272
+
273
+ def process_query(self, query: str, user_id: str, session_id: str) -> str:
274
+ if not GOOGLE_API_KEY:
275
+ return "Error: Google API not configured."
276
+
277
+ session_key = f"{user_id}_{session_id}"
278
+ user_sessions.setdefault(session_key, {"history": []})
279
+
280
+ tool_name, args = self._classify_intent(query)
281
+
282
+ if tool_name not in self.tools:
283
+ return f"I couldn't understand your request. Please try asking for:\n- Daily coding question\n- Mock interview\n- Interview questions for a specific topic"
284
+
285
+ result = self.tools[tool_name](**args)
286
+
287
+ user_sessions[session_key]["history"].append({
288
+ "query": query,
289
+ "response": result["response"]
290
+ })
291
+
292
+ return result["response"]
293
+
294
+ # β€”β€”β€” FastAPI Setup β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
295
+
296
+ app = FastAPI(title="Interview Prep API", version="2.0.0")
297
+ agent = InterviewPrepAgent()
298
+
299
+ class ChatRequest(BaseModel):
300
+ user_id: str
301
+ session_id: str
302
+ question: str
303
+
304
+ class ChatResponse(BaseModel):
305
+ session_id: str
306
+ answer: str
307
+
308
+ @app.post("/chat", response_model=ChatResponse)
309
+ async def chat(req: ChatRequest):
310
+ q = preprocess_query(req.question)
311
+ ans = agent.process_query(q, req.user_id, req.session_id)
312
+ return ChatResponse(session_id=req.session_id, answer=ans)
313
+
314
+ @app.get("/healthz")
315
+ def health():
316
+ status = {"status": "ok", "google_api": bool(GOOGLE_API_KEY),
317
+ "leetcode_count": len(LEETCODE_DATA),
318
+ "tavily": bool(TAVILY_API_KEY)}
319
+ return status
320
+
321
+ @app.get("/")
322
+ def root():
323
+ return {"message": "Interview Prep API v2", "endpoints": ["/chat", "/healthz"]}
324
+
325
+ if __name__ == "__main__":
326
+ import uvicorn
327
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ google-generativeai
3
+ langchain-tavily
4
+ gdown
5
+ pandas
6
+ openpyxl
7
+ python-dotenv
8
+ fastapi
9
+ uvicorn