-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
195 lines (155 loc) · 7.36 KB
/
Copy pathapp.py
File metadata and controls
195 lines (155 loc) · 7.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
from flask import Flask, render_template, request, jsonify
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import joblib
import os
import sys
import time
from openai import OpenAI
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Initialize Flask app
app = Flask(__name__)
# Initialize OpenAI client
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
# Global variable to store embeddings
df = None
def load_embeddings():
"""Load embeddings from file."""
global df
if not os.path.exists('embeddings.joblib'):
raise FileNotFoundError("embeddings.joblib file not found. Please run preprocess.py first.")
print("Loading embeddings...")
df = joblib.load('embeddings.joblib')
print(f"Loaded {len(df)} embeddings successfully")
return df
def create_embedding(text_list, max_retries=3):
"""Create embeddings using OpenAI API with error handling and retry logic."""
for attempt in range(max_retries):
try:
print(f"Creating embedding (attempt {attempt + 1}/{max_retries})...")
# Use OpenAI embeddings API
response = client.embeddings.create(
model=os.getenv('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
input=text_list,
dimensions=1024
)
# Extract embeddings from response
embeddings = [data.embedding for data in response.data]
print("Embedding created successfully")
return embeddings
except Exception as e:
print(f"Error creating embeddings (attempt {attempt + 1}): {e}")
if attempt == max_retries - 1:
raise Exception("All retry attempts failed. Please check your OpenAI API key and try again.")
time.sleep(2) # Wait before retry
def inference(prompt, max_retries=3):
"""Generate response using OpenAI API with error handling and retry logic."""
for attempt in range(max_retries):
try:
print(f"Generating response (attempt {attempt + 1}/{max_retries})...")
# Use OpenAI chat completions API
response = client.chat.completions.create(
model=os.getenv('OPENAI_CHAT_MODEL', 'gpt-3.5-turbo'),
messages=[
{"role": "system", "content": "You are a helpful mathematics tutor. Answer questions about geometry and transformations based on the provided video content."},
{"role": "user", "content": prompt}
],
max_tokens=int(os.getenv('OPENAI_MAX_TOKENS', 1000)),
temperature=float(os.getenv('OPENAI_TEMPERATURE', 0.7))
)
# Extract response content
response_text = response.choices[0].message.content
print("Generated response successfully")
return {"response": response_text}
except Exception as e:
print(f"Error generating response (attempt {attempt + 1}): {e}")
if attempt == max_retries - 1:
raise Exception("All retry attempts failed. Please check your OpenAI API key and try again.")
time.sleep(2) # Wait before retry
def create_fallback_response(df, query):
"""Create a simple fallback response when the generation API is unavailable."""
response_parts = []
response_parts.append(f"Based on your question '{query}', I found the following relevant video content:\n")
for idx, row in df.iterrows():
video_title = row['title']
start_time = int(row['start'])
end_time = int(row['end'])
text_content = row['text']
# Format time as MM:SS
start_formatted = f"{start_time//60}:{start_time%60:02d}"
end_formatted = f"{end_time//60}:{end_time%60:02d}"
response_parts.append(f"📹 {video_title}")
response_parts.append(f" Time: {start_formatted} - {end_formatted}")
response_parts.append(f" Content: {text_content}")
response_parts.append("")
response_parts.append("Note: This is a simplified response. For a more detailed answer, please ensure the generation API is working properly.")
return "\n".join(response_parts)
def process_query(incoming_query):
"""Process a single query and return response."""
global df
if df is None:
raise Exception("Embeddings not loaded")
print(f"Processing query: {incoming_query}")
# Create embedding for the question
question_embedding = create_embedding([incoming_query])[0]
# Find similarities
embeddings_array = np.vstack(df['embedding'])
question_embedding_array = np.array([question_embedding])
# Normalize embeddings to prevent overflow/underflow issues
embeddings_array = embeddings_array / (np.linalg.norm(embeddings_array, axis=1, keepdims=True) + 1e-8)
question_embedding_array = question_embedding_array / (np.linalg.norm(question_embedding_array, axis=1, keepdims=True) + 1e-8)
similarities = cosine_similarity(embeddings_array, question_embedding_array).flatten()
top_results = 5
max_indx = similarities.argsort()[::-1][0:top_results]
new_df = df.loc[max_indx]
# Create prompt
prompt = f'''I am teaching Mathematics in my Math Class course. Here are video subtitle chunks containing video title, video number, start time in seconds, end time in seconds, the text at that time:
{new_df[["title", "number", "start", "end", "text"]].to_json(orient="records")}
---------------------------------
"{incoming_query}"
User asked this question related to the video chunks, you have to answer in a human way (dont mention the above format, its just for you) where and how much content is taught in which video (in which video and at what timestamp) and guide the user to go to that particular video. If user asks unrelated question, tell him that you can only answer questions related to the course
'''
# Generate response
try:
response_data = inference(prompt)
response = response_data["response"]
except Exception as e:
print(f"API error: {e}")
# Fallback response when API times out
response = create_fallback_response(new_df, incoming_query)
return response
# Routes
@app.route('/')
def index():
"""Serve the main page."""
return render_template('index.html')
@app.route('/api/chat', methods=['POST'])
def chat():
"""Handle chat messages."""
try:
data = request.get_json()
message = data.get('message', '').strip()
if not message:
return jsonify({'error': 'Message cannot be empty'}), 400
# Process the query
response = process_query(message)
return jsonify({'response': response})
except Exception as e:
print(f"Error in chat endpoint: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/health')
def health():
"""Health check endpoint."""
return jsonify({'status': 'healthy', 'embeddings_loaded': df is not None})
if __name__ == '__main__':
try:
# Load embeddings on startup
load_embeddings()
print("Starting Flask app...")
app.run(debug=True, host='0.0.0.0', port=8080)
except Exception as e:
print(f"Failed to start app: {e}")
sys.exit(1)