ckharche commited on
Commit
2063825
·
verified ·
1 Parent(s): 118610a

Upload 13 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/android-chrome-512x512.png filter=lfs diff=lfs merge=lfs -text
MetaAIAPI_app.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from transformers import pipeline
3
+ from meta_ai_api import MetaAI
4
+ import os
5
+ import re # For text processing
6
+
7
+ # Initialize Meta AI client
8
+ client = MetaAI()
9
+
10
+ # Category list (unchanged)
11
+ categories = [
12
+ "Banking", "Books", "Clothes", "College Admissions", "Cooking",
13
+ "Elementary Education", "Middle School Education", "High School Education", "University Education",
14
+ "Employment", "Finance", "Food", "Gardening", "Homelessness", "Housing", "Jobs", "Investing",
15
+ "Matrimonial", "Brain Medical", "Depression Medical", "Eye Medical", "Hand Medical",
16
+ "Head Medical", "Leg Medical", "Rental", "School", "Shopping",
17
+ "Baseball Sports", "Basketball Sports", "Cricket Sports", "Handball Sports",
18
+ "Jogging Sports", "Hockey Sports", "Running Sports", "Tennis Sports",
19
+ "Stocks", "Travel", "Tourism"
20
+ ]
21
+
22
+ # Zero-shot classification pipeline (unchanged)
23
+ classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
24
+
25
+ app = Flask(__name__)
26
+
27
+ @app.route('/')
28
+ def home():
29
+ return render_template('index.html', categories=categories)
30
+
31
+ @app.route('/predict_categories', methods=['POST'])
32
+ def predict_categories():
33
+ data = request.get_json()
34
+ subject = data.get("subject")
35
+ description = data.get("description")
36
+ if not subject or not description:
37
+ return jsonify({"error": "Subject and description required"}), 400
38
+ prompt = f"{subject}. {description}"
39
+ try:
40
+ result = classifier(prompt, categories)
41
+ return jsonify({"predicted_categories": result['labels'][:3]})
42
+ except Exception as e:
43
+ return jsonify({"error": str(e)}), 500
44
+
45
+ @app.route('/generate_answer', methods=['POST'])
46
+ def generate_answer():
47
+ data = request.get_json()
48
+ category = data.get("category")
49
+ question = data.get("question")
50
+ if not category or not question:
51
+ return jsonify({"error": "Category and question required"}), 400
52
+
53
+ # Create a more specific prompt with formatting instructions
54
+ prompt = (
55
+ f"Category: {category}\n"
56
+ f"Question: {question}\n\n"
57
+ "Please provide a detailed and well-structured answer. Format your response as follows:\n"
58
+ "- For any list of items (e.g., websites, tips, steps, examples, or recommendations), use bullet points starting with '-' (e.g., - Item).\n"
59
+ "- Use bold text (e.g., **text**) for headings or key terms, such as section titles or important concepts.\n"
60
+ "- Use line breaks to separate paragraphs or sections for clarity.\n"
61
+ "- Ensure the response is clear, concise, and easy to read.\n"
62
+ "- If the question asks for recommendations, resources, or a list, always present them as bullet points.\n"
63
+ "For example, if asked for websites, format the response like this:\n"
64
+ "**Websites**\n"
65
+ "- Website 1: Description.\n"
66
+ "- Website 2: Description.\n"
67
+ )
68
+
69
+ try:
70
+ # Get the response from Meta AI
71
+ response = client.prompt(prompt)
72
+ raw_answer = response['message'].strip()
73
+
74
+ # Format the response to ensure consistency
75
+ formatted_answer = format_response(raw_answer)
76
+
77
+ return jsonify({"answer": formatted_answer})
78
+ except Exception as e:
79
+ return jsonify({"error": str(e)}), 500
80
+
81
+ def format_response(text):
82
+ """
83
+ Format the raw AI response to ensure bullet points and structure, even if the AI doesn't follow instructions.
84
+ """
85
+ # Split the text into lines
86
+ lines = text.split('\n')
87
+ formatted_lines = []
88
+ in_list = False
89
+
90
+ # Patterns to detect list-like content
91
+ list_indicators = [
92
+ r'^(General|Niche|International|Country-Specific|Additional)\b', # Section headings
93
+ r'^(Indeed|LinkedIn|Glassdoor|H1BGrader|H1B Visa Jobs|ImmIhelp)\b', # Website names
94
+ r'^(Network|Check|Stay)\b' # Tips starting with verbs
95
+ ]
96
+
97
+ for line in lines:
98
+ line = line.strip()
99
+ if not line:
100
+ if in_list:
101
+ in_list = False
102
+ formatted_lines.append('') # Add a line break after a list
103
+ continue
104
+
105
+ # Check if the line starts a new section (e.g., "General Job Search Websites")
106
+ if any(re.match(pattern, line, re.IGNORECASE) for pattern in list_indicators):
107
+ if in_list:
108
+ in_list = False
109
+ formatted_lines.append('') # Add a line break before a new section
110
+ # Add the section as a bold heading
111
+ formatted_lines.append(f"**{line}**")
112
+ in_list = True
113
+ continue
114
+
115
+ # If we're in a list, format the line as a bullet point
116
+ if in_list and not line.startswith('-'):
117
+ # Split the line into website/tip and description (e.g., "Indeed (link unavailable): Description")
118
+ if ': ' in line:
119
+ parts = line.split(': ', 1)
120
+ item = parts[0].strip()
121
+ description = parts[1].strip() if len(parts) > 1 else ""
122
+ formatted_lines.append(f"- **{item}**: {description}")
123
+ else:
124
+ formatted_lines.append(f"- {line}")
125
+ else:
126
+ # If not in a list, add the line as a paragraph
127
+ if in_list:
128
+ in_list = False
129
+ formatted_lines.append('') # Add a line break after a list
130
+ formatted_lines.append(line)
131
+
132
+ # Join the lines with proper spacing
133
+ formatted_text = '\n'.join(formatted_lines)
134
+ return formatted_text
135
+
136
+ if __name__ == '__main__':
137
+ app.run(debug=True)
README.md CHANGED
@@ -1,12 +1,217 @@
1
- ---
2
- title: Saayam STT
3
- emoji: 🐨
4
- colorFrom: red
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: Testing Speech-to-Text Feature for Saayam Web UI
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # 🔍 Saayam AI Assistant 🤖
3
+
4
+ Saayam AI Assistant is a web-based application built with Flask that allows users to query various AI models (Meta AI, Gemini, ChatGPT, and groq) for answers across multiple categories (e.g., Jobs, Education, Finance). The application uses zero-shot classification to predict relevant categories for user queries and provides detailed, formatted responses. Additionally, it collects performance metrics (latency, speed, temperature, token counts) to compare the efficiency of each AI model.
5
+
6
+ ## 🧠 Features
7
+ - **Multi-Model Support**: Query Meta AI, Gemini, ChatGPT, or groq via a command-line argument.
8
+ - **Category Prediction**: Uses zero-shot classification (`facebook/bart-large-mnli`) to predict relevant categories for user queries.
9
+ - **Formatted Responses**: Responses are structured with bullet points, bold headings, and clear sections for readability.
10
+ - **Performance Metrics**: Measures latency (TTFT/TTLT), speed (tokens/second), temperature, and token counts for each model.
11
+ - **Web Interface**: A user-friendly interface built with Flask, HTML, and JavaScript, with Markdown rendering for responses.
12
+
13
+ ## 🔧 Setup Instructions
14
+
15
+ ### 1. Create & Activate Conda Environment
16
+ Create a Conda environment with Python 3.10 and activate it:
17
+
18
+ ```bash
19
+ conda create -n saayam-env python=3.10
20
+ conda activate saayam-env
21
+ ```
22
+
23
+ ### 2. Clone the Repository
24
+ Clone the project repository to your local machine:
25
+
26
+ ```bash
27
+ git clone https://github.com/RobuRishabh/Saayam_ai.git
28
+ cd Saayam_ai
29
+ ```
30
+
31
+ ### 3. Install Requirements
32
+ Install the required Python packages listed in requirements.txt:
33
+
34
+ ```bash
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ Note: Ensure you have the following packages in your requirements.txt:
39
+
40
+ ```
41
+ flask
42
+ transformers
43
+ meta-ai-api
44
+ google-generativeai
45
+ openai
46
+ groq
47
+ python-dotenv
48
+ tiktoken
49
+ ```
50
+
51
+ ### 4. Set Up Environment Variables
52
+ Create a .env file in the project root directory and add your API keys for Gemini, ChatGPT, and groq:
53
+
54
+ ```
55
+ GEMINI_API_KEY=your_gemini_api_key
56
+ OPENAI_API_KEY=your_openai_api_key
57
+ GROQ_API_KEY=your_groq_api_key
58
+ ```
59
+
60
+ Note: Meta AI doesn’t require an API key in this setup (uses meta-ai-api library).
61
+
62
+ ### 5. Run the Application
63
+ Run the application with a specific AI model using the --model argument. The available models are meta_ai, gemini, openai, and groq.
64
+
65
+ Meta AI:
66
+ ```bash
67
+ python app.py --model meta_ai
68
+ ```
69
+
70
+ Gemini:
71
+ ```bash
72
+ python app.py --model gemini
73
+ ```
74
+
75
+ ChatGPT (OpenAI):
76
+ ```bash
77
+ python app.py --model openai
78
+ ```
79
+
80
+ groq:
81
+ ```bash
82
+ python app.py --model groq
83
+ ```
84
+
85
+ After running the application, open your browser and navigate to http://127.0.0.1:5000 to access the Saayam AI Assistant interface.
86
+
87
+ ## 📁 Project Structure
88
+
89
+ ```
90
+ Saayam_ai/
91
+ ├── app.py # Main application with multi-model support and metrics
92
+ ├── MetaAIAPI_app.py # Meta AI-only version (simpler implementation)
93
+ ├── templates/
94
+ │ └── index.html # Frontend HTML template
95
+ ├── static/
96
+ │ ├── apple-touch-icon.png
97
+ │ ├── favicon-16x16.png
98
+ │ ├── favicon-32x32.png
99
+ │ ├── favicon.ico
100
+ │ └── site.webmanifest # Web manifest for favicon and icons
101
+ ├── requirements.txt # Python dependencies
102
+ ├── .env # Environment variables (API keys)
103
+ ├── model_metrics.log # Log file for performance metrics
104
+ └── .gitignore # Git ignore file
105
+ ```
106
+
107
+ ## 📊 API Performance Evaluation
108
+ The application collects performance metrics for each AI model, including latency, speed, temperature, and token counts. The metrics were evaluated using the query "Suggest me good job searching websites for international students" in the "Jobs" category.
109
+
110
+ ### Performance Metrics
111
+
112
+ **Meta AI**:
113
+ - Model: meta_ai
114
+ - Temperature: 0.7 (default)
115
+ - Time to First Token (TTFT): 15.185 seconds
116
+ - Total Response Time (TTLT): 15.185 seconds
117
+ - Speed: 20.81 tokens/second
118
+ - Input Tokens: 127
119
+ - Output Tokens: 316
120
+
121
+ **Gemini**:
122
+ - Model: gemini
123
+ - Temperature: 0.7
124
+ - Time to First Token (TTFT): 4.515 seconds
125
+ - Total Response Time (TTLT): 4.515 seconds
126
+ - Speed: 100.32 tokens/second
127
+ - Input Tokens: 127
128
+ - Output Tokens: 453
129
+
130
+ **ChatGPT (OpenAI)**:
131
+ - Model: openai
132
+ - Temperature: 0.7
133
+ - Time to First Token (TTFT): 4.619 seconds
134
+ - Total Response Time (TTLT): 4.619 seconds
135
+ - Speed: 81.83 tokens/second
136
+ - Input Tokens: 176
137
+ - Output Tokens: 378
138
+
139
+ **groq**:
140
+ - Model: groq
141
+ - Temperature: 0.7
142
+ - Time to First Token (TTFT): 0.856 seconds
143
+ - Total Response Time (TTLT): 0.856 seconds
144
+ - Speed: 630.66 tokens/second
145
+ - Input Tokens: 127
146
+ - Output Tokens: 540
147
+
148
+ ### Cost Analysis
149
+
150
+ - **Meta AI**: Free (unofficial API), but may have rate limits or reliability issues.
151
+ - **Gemini**: Free tier available, with paid plans for higher usage.
152
+ - **ChatGPT**: Pay-per-use ($0.002 per 1K tokens for gpt-3.5-turbo).
153
+ - **groq**: Free tier available, with paid plans for higher usage.
154
+
155
+ ### Limitations
156
+
157
+ - **Meta AI**: Slow, low speed, lacks temperature control.
158
+ - **Gemini**: Moderate performance, potential tokenization differences.
159
+ - **ChatGPT**: Reliable, slightly slower than groq.
160
+ - **groq**: Fastest, high output token count (verbosity).
161
+
162
+ ## ⚖️ Comparison with Alternative Solutions
163
+
164
+ | Model | Speed (tokens/s) | TTLT (s) | Cost | Quality |
165
+ |-----------|------------------|----------|--------------|------------------------|
166
+ | Meta AI | 20.81 | 15.185 | Free | Least consistent |
167
+ | Gemini | 100.32 | 4.515 | Free tier | Moderate consistency |
168
+ | ChatGPT | 81.83 | 4.619 | Pay-per-use | Highly consistent |
169
+ | groq | 630.66 | 0.856 | Free tier | Practical, fast |
170
+
171
+ ## 🛠️ Proof-of-Concept Implementation
172
+
173
+ ### Overview
174
+ Flask-based web app to query 4 models, classify categories, format answers, and log metrics.
175
+
176
+ ### Backend (`app.py`)
177
+ - Flask app, handles routes `/predict_categories` and `/generate_answer`
178
+ - Model passed using `--model` CLI argument
179
+ - Collects and logs metrics (TTFT, TTLT, token counts, temperature)
180
+
181
+ ### Frontend (`index.html`)
182
+ - HTML + JavaScript interface
183
+ - Markdown rendering using marked.js
184
+ - Submits subject, description, category
185
+ - Displays response + metrics
186
+
187
+ ### Example
188
+ ```bash
189
+ python app.py --model groq
190
+ ```
191
+ Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
192
+
193
+ ## 📈 Analysis and Recommendations
194
+
195
+ - **Fastest**: groq (0.856s TTLT, 630.66 tokens/s)
196
+ - **Most Consistent**: ChatGPT
197
+ - **Cost-Effective**: Gemini & groq
198
+ - **Slowest**: Meta AI
199
+
200
+ ### Recommendations
201
+
202
+ - Use **groq** for real-time speed
203
+ - Use **ChatGPT** for reliability & consistency
204
+ - Use **Gemini** for cost-conscious performance
205
+ - Avoid **Meta AI** for production
206
+
207
+ ## 🚀 Future Improvements
208
+
209
+ - Enable streaming for better TTFT
210
+ - Add cosine similarity for response sensitivity
211
+ - Load testing (e.g., locust)
212
+ - Caching frequent queries to save cost
213
+
214
+ ## 🙌 Acknowledgments
215
+
216
+ Built with ❤️ using Flask, Transformers, and AI APIs.
217
+ Special thanks to the open-source contributors of meta-ai-api.
app.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from transformers import pipeline
3
+ from meta_ai_api import MetaAI
4
+ import google.generativeai as genai # Gemini API
5
+ from openai import OpenAI # OpenAI API
6
+ from groq import Groq # groq API
7
+ from huggingface_hub import InferenceClient # Hugging Face API
8
+ from dotenv import load_dotenv
9
+ import os
10
+ import re
11
+ import argparse
12
+ import time
13
+ import tiktoken
14
+ import logging
15
+ import transformers
16
+ import tempfile
17
+
18
+ # Suppress transformers logging to reduce clutter
19
+ transformers.logging.set_verbosity_error()
20
+
21
+ # Load environment variables from .env
22
+ load_dotenv()
23
+
24
+ # Set up logging to a file for metrics
25
+ logging.basicConfig(
26
+ filename='model_metrics.log',
27
+ level=logging.INFO,
28
+ format='%(asctime)s - %(message)s'
29
+ )
30
+
31
+ # Ensure Flask's logger outputs to the console
32
+ console_handler = logging.StreamHandler()
33
+ console_handler.setLevel(logging.INFO)
34
+ werkzeug_logger = logging.getLogger('werkzeug')
35
+ werkzeug_logger.addHandler(console_handler)
36
+
37
+ # Parse command-line arguments
38
+ parser = argparse.ArgumentParser(description="Run Saayam AI Assistant with a specific AI model.")
39
+ parser.add_argument(
40
+ "--model",
41
+ type=str,
42
+ choices=["meta_ai", "gemini", "openai", "groq"],
43
+ default="meta_ai",
44
+ help="Choose the AI model to use: meta_ai, gemini, openai, or groq"
45
+ )
46
+ args = parser.parse_args()
47
+ selected_model = args.model
48
+
49
+ # Default temperature for models that support it
50
+ DEFAULT_TEMPERATURE = 0.7
51
+
52
+ # Initialize the chosen AI client
53
+ if selected_model == "meta_ai":
54
+ ai_client = MetaAI()
55
+ elif selected_model == "gemini":
56
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
57
+ genai.configure(api_key=gemini_api_key)
58
+ ai_client = genai.GenerativeModel('gemini-1.5-flash')
59
+ elif selected_model == "openai":
60
+ ai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
61
+ elif selected_model == "groq":
62
+ groq_api_key = os.getenv("GROQ_API_KEY")
63
+ ai_client = Groq(api_key=groq_api_key)
64
+
65
+ # Initialize Hugging Face client for Speech-to-Text
66
+ hf_client = InferenceClient(token=os.getenv("HF_API_KEY"))
67
+
68
+ # Category list (unchanged)
69
+ categories = [
70
+ "Banking", "Books", "Clothes", "College Admissions", "Cooking",
71
+ "Elementary Education", "Middle School Education", "High School Education", "University Education",
72
+ "Employment", "Finance", "Food", "Gardening", "Homelessness", "Housing", "Jobs", "Investing",
73
+ "Matrimonial", "Brain Medical", "Depression Medical", "Eye Medical", "Hand Medical",
74
+ "Head Medical", "Leg Medical", "Rental", "School", "Shopping",
75
+ "Baseball Sports", "Basketball Sports", "Cricket Sports", "Handball Sports",
76
+ "Jogging Sports", "Hockey Sports", "Running Sports", "Tennis Sports",
77
+ "Stocks", "Travel", "Tourism"
78
+ ]
79
+
80
+ # Zero-shot classification pipeline (unchanged)
81
+ classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
82
+
83
+ # Initialize tokenizer for token counting (for OpenAI models)
84
+ try:
85
+ openai_tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")
86
+ except:
87
+ openai_tokenizer = None # Fallback if tiktoken fails
88
+
89
+ app = Flask(__name__)
90
+
91
+ @app.route('/')
92
+ def home():
93
+ return render_template('index.html', categories=categories)
94
+
95
+ @app.route('/transcribe', methods=['POST'])
96
+ def transcribe_audio():
97
+ """
98
+ Transcribe audio using Whisper Large V3 Turbo via Hugging Face Inference API.
99
+ """
100
+ if 'audio' not in request.files:
101
+ return jsonify({"error": "No audio file provided"}), 400
102
+
103
+ audio_file = request.files['audio']
104
+ temp_file_path = None
105
+
106
+ try:
107
+ # Read the audio bytes
108
+ audio_bytes = audio_file.read()
109
+
110
+ # Detect if it's video/webm and use .ogg instead to force audio interpretation
111
+ content_type = audio_file.content_type or 'audio/webm'
112
+
113
+ # Force .ogg extension to avoid video/webm interpretation
114
+ if 'webm' in content_type:
115
+ suffix = '.ogg'
116
+ elif 'wav' in content_type:
117
+ suffix = '.wav'
118
+ elif 'ogg' in content_type:
119
+ suffix = '.ogg'
120
+ else:
121
+ suffix = '.webm'
122
+
123
+ # Save to a temporary file
124
+ temp_dir = "/tmp/audio"
125
+ if not os.path.exists(temp_dir):
126
+ os.makedirs(temp_dir)
127
+
128
+ suffix = '.ogg'
129
+ # Use os.path.join for explicit path control in container
130
+ temp_file_path = os.path.join(temp_dir, f"recording_{int(time.time())}{suffix}")
131
+
132
+ with open(temp_file_path, "wb") as f:
133
+ f.write(audio_bytes)
134
+
135
+ # Pass the file path to the Hugging Face client
136
+ result = hf_client.automatic_speech_recognition(
137
+ temp_file_path,
138
+ model="openai/whisper-large-v3-turbo"
139
+ )
140
+
141
+ # Clean up the temporary file
142
+ if temp_file_path and os.path.exists(temp_file_path):
143
+ os.unlink(temp_file_path)
144
+
145
+ # Extract transcribed text
146
+ transcribed_text = ""
147
+ if isinstance(result, dict) and 'text' in result:
148
+ transcribed_text = result['text']
149
+ elif hasattr(result, 'text'):
150
+ transcribed_text = result.text
151
+ elif isinstance(result, str):
152
+ transcribed_text = result
153
+ else:
154
+ transcribed_text = str(result)
155
+
156
+ return jsonify({
157
+ "text": transcribed_text.strip()
158
+ })
159
+
160
+ except Exception as e:
161
+ # Clean up temp file if it exists
162
+ if temp_file_path and os.path.exists(temp_file_path):
163
+ os.unlink(temp_file_path)
164
+
165
+ import traceback
166
+ traceback.print_exc()
167
+ return jsonify({"error": f"Transcription failed: {str(e)}"}), 500
168
+
169
+
170
+ @app.route('/predict_categories', methods=['POST'])
171
+ def predict_categories():
172
+ data = request.get_json()
173
+ subject = data.get("subject")
174
+ description = data.get("description")
175
+ if not subject or not description:
176
+ return jsonify({"error": "Subject and description required"}), 400
177
+ prompt = f"{subject}. {description}"
178
+ try:
179
+ result = classifier(prompt, categories)
180
+ return jsonify({"predicted_categories": result['labels'][:3]})
181
+ except Exception as e:
182
+ return jsonify({"error": str(e)}), 500
183
+
184
+ @app.route('/generate_answer', methods=['POST'])
185
+ def generate_answer():
186
+ data = request.get_json()
187
+ category = data.get("category")
188
+ question = data.get("question")
189
+
190
+ if not category or not question:
191
+ return jsonify({"error": "Category and question required"}), 400
192
+
193
+ # Create a prompt with formatting instructions
194
+ prompt = (
195
+ f"Category: {category}\n"
196
+ f"Question: {question}\n\n"
197
+ "Please provide a detailed and well-structured answer. Format your response as follows:\n"
198
+ "- For any list of items (e.g., websites, tips, steps, examples, or recommendations), use bullet points starting with '-' (e.g., - Item).\n"
199
+ "- Use bold text (e.g., **text**) for headings or key terms, such as section titles or important concepts.\n"
200
+ "- Use line breaks to separate paragraphs or sections for clarity.\n"
201
+ "- Ensure the response is clear, concise, and easy to read.\n"
202
+ "- If the question asks for recommendations, resources, or a list, always present them as bullet points.\n"
203
+ "For example, if asked for websites, format the response like this:\n"
204
+ "**Websites**\n"
205
+ "- Website 1: Description.\n"
206
+ "- Website 2: Description.\n"
207
+ )
208
+
209
+ # Count input tokens (approximation for non-OpenAI models)
210
+ if openai_tokenizer:
211
+ input_tokens = len(openai_tokenizer.encode(prompt))
212
+ else:
213
+ input_tokens = len(prompt.split())
214
+
215
+ # Measure latency and generate response
216
+ try:
217
+ start_time = time.time()
218
+ first_token_time = None
219
+ raw_answer = None
220
+
221
+ if selected_model == "meta_ai":
222
+ response = ai_client.prompt(prompt)
223
+ raw_answer = response['message'].strip()
224
+ first_token_time = time.time()
225
+ elif selected_model == "gemini":
226
+ response = ai_client.generate_content(prompt, generation_config={"temperature": DEFAULT_TEMPERATURE})
227
+ raw_answer = response.text.strip()
228
+ first_token_time = time.time()
229
+ elif selected_model == "openai":
230
+ response = ai_client.chat.completions.create(
231
+ model="gpt-3.5-turbo",
232
+ messages=[
233
+ {"role": "system", "content": "You are a helpful assistant."},
234
+ {"role": "user", "content": prompt}
235
+ ],
236
+ temperature=DEFAULT_TEMPERATURE
237
+ )
238
+ raw_answer = response.choices[0].message.content.strip()
239
+ first_token_time = time.time()
240
+ elif selected_model == "groq":
241
+ response = ai_client.chat.completions.create(
242
+ model="llama-3.1-8b-instant",
243
+ messages=[
244
+ {"role": "system", "content": "You are a helpful assistant."},
245
+ {"role": "user", "content": prompt}
246
+ ],
247
+ temperature=DEFAULT_TEMPERATURE
248
+ )
249
+ raw_answer = response.choices[0].message.content.strip()
250
+ first_token_time = time.time()
251
+
252
+ end_time = time.time()
253
+
254
+ # Calculate latency
255
+ ttft = first_token_time - start_time
256
+ ttlt = end_time - start_time
257
+
258
+ # Format the response
259
+ formatted_answer = format_response(raw_answer)
260
+
261
+ # Count output tokens
262
+ if openai_tokenizer:
263
+ output_tokens = len(openai_tokenizer.encode(raw_answer))
264
+ else:
265
+ output_tokens = len(raw_answer.split())
266
+
267
+ # Calculate speed (tokens per second)
268
+ speed = output_tokens / ttlt if ttlt > 0 else 0
269
+
270
+ # Log metrics
271
+ metrics = {
272
+ "model": selected_model,
273
+ "temperature": DEFAULT_TEMPERATURE if selected_model in ["gemini", "openai", "groq"] else "N/A",
274
+ "ttft_seconds": round(ttft, 3),
275
+ "ttlt_seconds": round(ttlt, 3),
276
+ "speed_tokens_per_second": round(speed, 2),
277
+ "input_tokens": input_tokens,
278
+ "output_tokens": output_tokens
279
+ }
280
+ logging.info(f"Metrics: {metrics}")
281
+
282
+ # Return the answer and metrics
283
+ return jsonify({
284
+ "answer": formatted_answer,
285
+ "metrics": metrics
286
+ })
287
+ except Exception as e:
288
+ return jsonify({"error": str(e)}), 500
289
+
290
+ def format_response(text):
291
+ lines = text.split('\n')
292
+ formatted_lines = []
293
+ in_list = False
294
+
295
+ list_indicators = [
296
+ r'^(General|Niche|International|Country-Specific|Additional)\b',
297
+ r'^(Indeed|LinkedIn|Glassdoor|H1BGrader|H1B Visa Jobs|ImmIhelp)\b',
298
+ r'^(Network|Check|Stay)\b'
299
+ ]
300
+
301
+ for line in lines:
302
+ line = line.strip()
303
+ if not line:
304
+ if in_list:
305
+ in_list = False
306
+ formatted_lines.append('')
307
+ continue
308
+
309
+ if any(re.match(pattern, line, re.IGNORECASE) for pattern in list_indicators):
310
+ if in_list:
311
+ in_list = False
312
+ formatted_lines.append('')
313
+ formatted_lines.append(f"**{line}**")
314
+ in_list = True
315
+ continue
316
+
317
+ if in_list and not line.startswith('-'):
318
+ if ': ' in line:
319
+ parts = line.split(': ', 1)
320
+ item = parts[0].strip()
321
+ description = parts[1].strip() if len(parts) > 1 else ""
322
+ formatted_lines.append(f"- **{item}**: {description}")
323
+ else:
324
+ formatted_lines.append(f"- {line}")
325
+ else:
326
+ if in_list:
327
+ in_list = False
328
+ formatted_lines.append('')
329
+ formatted_lines.append(line)
330
+
331
+ formatted_text = '\n'.join(formatted_lines)
332
+ return formatted_text
333
+
334
+ if __name__ == '__main__':
335
+ print(f"Starting Saayam AI Assistant with model: {selected_model}")
336
+ app.run(debug=True, host='127.0.0.1', port=5000)
model_metrics.log ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025-11-20 15:13:05,032 - Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`
2
+ 2025-11-20 15:18:21,164 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
3
+ * Running on http://127.0.0.1:5000
4
+ 2025-11-20 15:18:21,164 - Press CTRL+C to quit
5
+ 2025-11-20 15:18:21,167 - * Restarting with stat
6
+ 2025-11-20 15:18:35,608 - * Debugger is active!
7
+ 2025-11-20 15:18:35,612 - * Debugger PIN: 465-628-698
8
+ 2025-11-20 15:18:35,710 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET / HTTP/1.1" 200 -
9
+ 2025-11-20 15:18:35,965 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET /static/favicon.ico HTTP/1.1" 200 -
10
+ 2025-11-20 15:18:35,968 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET /static/site.webmanifest HTTP/1.1" 200 -
11
+ 2025-11-20 15:19:05,508 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 10.035, 'ttlt_seconds': 10.035, 'speed_tokens_per_second': 3.29, 'input_tokens': 115, 'output_tokens': 33}
12
+ 2025-11-20 15:19:05,510 - 127.0.0.1 - - [20/Nov/2025 15:19:05] "POST /generate_answer HTTP/1.1" 200 -
13
+ 2025-11-20 15:19:08,871 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 3.13, 'ttlt_seconds': 3.13, 'speed_tokens_per_second': 14.7, 'input_tokens': 115, 'output_tokens': 46}
14
+ 2025-11-20 15:19:08,872 - 127.0.0.1 - - [20/Nov/2025 15:19:08] "POST /generate_answer HTTP/1.1" 200 -
15
+ 2025-11-20 15:19:59,788 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 7.48, 'ttlt_seconds': 7.48, 'speed_tokens_per_second': 26.07, 'input_tokens': 126, 'output_tokens': 195}
16
+ 2025-11-20 15:19:59,788 - 127.0.0.1 - - [20/Nov/2025 15:19:59] "POST /generate_answer HTTP/1.1" 200 -
17
+ 2025-11-20 15:20:48,377 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
18
+ * Running on http://127.0.0.1:5000
19
+ 2025-11-20 15:20:48,377 - Press CTRL+C to quit
20
+ 2025-11-20 15:20:48,379 - * Restarting with stat
21
+ 2025-11-20 15:21:03,705 - * Debugger is active!
22
+ 2025-11-20 15:21:03,712 - * Debugger PIN: 465-628-698
23
+ 2025-11-20 15:21:03,747 - 127.0.0.1 - - [20/Nov/2025 15:21:03] "GET / HTTP/1.1" 200 -
24
+ 2025-11-20 15:21:04,891 - 127.0.0.1 - - [20/Nov/2025 15:21:04] "GET /static/site.webmanifest HTTP/1.1" 304 -
25
+ 2025-11-20 15:23:15,016 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 400 Bad Request"
26
+ 2025-11-20 15:23:15,017 - 127.0.0.1 - - [20/Nov/2025 15:23:15] "POST /generate_answer HTTP/1.1" 500 -
27
+ 2025-11-20 15:24:09,660 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
28
+ 2025-11-20 15:24:10,236 - * Restarting with stat
29
+ 2025-11-20 15:24:17,104 - * Debugger is active!
30
+ 2025-11-20 15:24:17,106 - * Debugger PIN: 465-628-698
31
+ 2025-11-20 15:24:29,466 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 404 Not Found"
32
+ 2025-11-20 15:24:29,468 - 127.0.0.1 - - [20/Nov/2025 15:24:29] "POST /generate_answer HTTP/1.1" 500 -
33
+ 2025-11-20 15:25:10,940 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
34
+ 2025-11-20 15:25:11,864 - * Restarting with stat
35
+ 2025-11-20 15:25:20,254 - * Debugger is active!
36
+ 2025-11-20 15:25:20,257 - * Debugger PIN: 465-628-698
37
+ 2025-11-20 15:26:01,027 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 200 OK"
38
+ 2025-11-20 15:26:01,071 - Metrics: {'model': 'grok', 'temperature': 0.7, 'ttft_seconds': 1.227, 'ttlt_seconds': 1.227, 'speed_tokens_per_second': 301.62, 'input_tokens': 126, 'output_tokens': 370}
39
+ 2025-11-20 15:26:01,075 - 127.0.0.1 - - [20/Nov/2025 15:26:01] "POST /generate_answer HTTP/1.1" 200 -
40
+ 2025-11-21 15:12:43,192 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
41
+ * Running on http://127.0.0.1:5000
42
+ 2025-11-21 15:12:43,192 - Press CTRL+C to quit
43
+ 2025-11-21 15:12:43,194 - * Restarting with stat
44
+ 2025-11-21 15:12:49,726 - * Debugger is active!
45
+ 2025-11-21 15:12:49,734 - * Debugger PIN: 465-628-698
46
+ 2025-11-21 15:12:49,781 - 127.0.0.1 - - [21/Nov/2025 15:12:49] "GET / HTTP/1.1" 200 -
47
+ 2025-11-21 15:12:49,996 - 127.0.0.1 - - [21/Nov/2025 15:12:49] "GET /static/site.webmanifest HTTP/1.1" 200 -
48
+ 2025-11-21 15:13:30,295 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 200 OK"
49
+ 2025-11-21 15:13:30,341 - Metrics: {'model': 'groq', 'temperature': 0.7, 'ttft_seconds': 1.451, 'ttlt_seconds': 1.451, 'speed_tokens_per_second': 298.44, 'input_tokens': 134, 'output_tokens': 433}
50
+ 2025-11-21 15:13:30,342 - 127.0.0.1 - - [21/Nov/2025 15:13:30] "POST /generate_answer HTTP/1.1" 200 -
51
+ 2025-11-21 16:41:31,251 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
52
+ * Running on http://127.0.0.1:5000
53
+ 2025-11-21 16:41:31,252 - Press CTRL+C to quit
54
+ 2025-11-21 16:41:31,256 - * Restarting with stat
55
+ 2025-11-21 16:41:57,973 - * Debugger is active!
56
+ 2025-11-21 16:41:57,983 - * Debugger PIN: 465-628-698
57
+ 2025-11-21 16:41:58,031 - 127.0.0.1 - - [21/Nov/2025 16:41:58] "GET / HTTP/1.1" 200 -
58
+ 2025-11-21 16:41:58,408 - 127.0.0.1 - - [21/Nov/2025 16:41:58] "GET /static/site.webmanifest HTTP/1.1" 304 -
59
+ 2025-11-21 16:48:14,113 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "GET / HTTP/1.1" 200 -
60
+ 2025-11-21 16:48:14,330 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "GET /static/site.webmanifest HTTP/1.1" 304 -
61
+ 2025-11-21 16:48:14,333 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "GET /static/favicon.ico HTTP/1.1" 200 -
62
+ 2025-11-21 16:48:23,602 - 127.0.0.1 - - [21/Nov/2025 16:48:23] "POST /transcribe HTTP/1.1" 500 -
63
+ 2025-11-21 16:48:51,478 - 127.0.0.1 - - [21/Nov/2025 16:48:51] "GET /static/site.webmanifest HTTP/1.1" 304 -
64
+ 2025-11-21 16:48:51,483 - 127.0.0.1 - - [21/Nov/2025 16:48:51] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
65
+ 2025-11-21 16:48:57,299 - 127.0.0.1 - - [21/Nov/2025 16:48:57] "POST /transcribe HTTP/1.1" 500 -
66
+ 2025-11-21 17:00:33,452 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
67
+ 2025-11-21 17:00:34,779 - * Restarting with stat
68
+ 2025-11-21 17:05:05,788 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
69
+ * Running on http://127.0.0.1:5000
70
+ 2025-11-21 17:05:05,789 - Press CTRL+C to quit
71
+ 2025-11-21 17:05:05,793 - * Restarting with stat
72
+ 2025-11-21 17:05:24,987 - * Debugger is active!
73
+ 2025-11-21 17:05:24,993 - * Debugger PIN: 465-628-698
74
+ 2025-11-21 17:05:31,454 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET / HTTP/1.1" 200 -
75
+ 2025-11-21 17:05:31,511 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
76
+ 2025-11-21 17:05:31,753 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET /static/favicon.ico HTTP/1.1" 200 -
77
+ 2025-11-21 17:05:31,755 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET /static/site.webmanifest HTTP/1.1" 304 -
78
+ 2025-11-21 17:05:43,532 - 127.0.0.1 - - [21/Nov/2025 17:05:43] "POST /transcribe HTTP/1.1" 500 -
79
+ 2025-11-21 17:05:50,954 - 127.0.0.1 - - [21/Nov/2025 17:05:50] "GET / HTTP/1.1" 200 -
80
+ 2025-11-21 17:05:51,252 - 127.0.0.1 - - [21/Nov/2025 17:05:51] "GET /static/site.webmanifest HTTP/1.1" 304 -
81
+ 2025-11-21 17:05:53,650 - 127.0.0.1 - - [21/Nov/2025 17:05:53] "GET /static/site.webmanifest HTTP/1.1" 304 -
82
+ 2025-11-21 17:05:53,669 - 127.0.0.1 - - [21/Nov/2025 17:05:53] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
83
+ 2025-11-21 17:06:07,126 - 127.0.0.1 - - [21/Nov/2025 17:06:07] "POST /transcribe HTTP/1.1" 500 -
84
+ 2025-11-21 17:06:20,044 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
85
+ 2025-11-21 17:06:21,979 - * Restarting with stat
86
+ 2025-11-21 17:06:53,632 - * Debugger is active!
87
+ 2025-11-21 17:06:53,642 - * Debugger PIN: 465-628-698
88
+ 2025-11-21 17:06:53,698 - 127.0.0.1 - - [21/Nov/2025 17:06:53] "GET / HTTP/1.1" 200 -
89
+ 2025-11-21 17:06:53,756 - 127.0.0.1 - - [21/Nov/2025 17:06:53] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
90
+ 2025-11-21 17:06:54,076 - 127.0.0.1 - - [21/Nov/2025 17:06:54] "GET /static/site.webmanifest HTTP/1.1" 304 -
91
+ 2025-11-21 17:06:54,078 - 127.0.0.1 - - [21/Nov/2025 17:06:54] "GET /static/favicon.ico HTTP/1.1" 200 -
92
+ 2025-11-21 17:09:47,214 - 127.0.0.1 - - [21/Nov/2025 17:09:47] "POST /transcribe HTTP/1.1" 500 -
93
+ 2025-11-21 17:24:06,935 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
94
+ 2025-11-21 17:24:08,306 - * Restarting with stat
95
+ 2025-11-21 17:24:30,142 - * Debugger is active!
96
+ 2025-11-21 17:24:30,144 - * Debugger PIN: 465-628-698
97
+ 2025-11-21 17:24:40,736 - 127.0.0.1 - - [21/Nov/2025 17:24:40] "POST /transcribe HTTP/1.1" 500 -
98
+ 2025-11-21 17:26:37,091 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
99
+ 2025-11-21 17:26:38,450 - * Restarting with stat
100
+ 2025-11-21 17:26:52,990 - * Debugger is active!
101
+ 2025-11-21 17:26:52,997 - * Debugger PIN: 465-628-698
102
+ 2025-11-21 17:27:10,347 - 127.0.0.1 - - [21/Nov/2025 17:27:10] "POST /transcribe HTTP/1.1" 500 -
103
+ 2025-11-21 17:27:25,067 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
104
+ * Running on http://127.0.0.1:5000
105
+ 2025-11-21 17:27:25,067 - Press CTRL+C to quit
106
+ 2025-11-21 17:27:25,069 - * Restarting with stat
107
+ 2025-11-21 17:27:35,951 - * Debugger is active!
108
+ 2025-11-21 17:27:35,956 - * Debugger PIN: 465-628-698
109
+ 2025-11-21 17:27:35,985 - 127.0.0.1 - - [21/Nov/2025 17:27:35] "GET / HTTP/1.1" 200 -
110
+ 2025-11-21 17:27:36,140 - 127.0.0.1 - - [21/Nov/2025 17:27:36] "GET /static/site.webmanifest HTTP/1.1" 304 -
111
+ 2025-11-21 17:28:38,222 - 127.0.0.1 - - [21/Nov/2025 17:28:38] "GET /static/site.webmanifest HTTP/1.1" 304 -
112
+ 2025-11-21 17:28:38,232 - 127.0.0.1 - - [21/Nov/2025 17:28:38] "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 -
113
+ 2025-11-21 17:28:56,242 - 127.0.0.1 - - [21/Nov/2025 17:28:56] "POST /transcribe HTTP/1.1" 500 -
114
+ 2025-11-21 17:34:01,152 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
115
+ * Running on http://127.0.0.1:5000
116
+ 2025-11-21 17:34:01,153 - Press CTRL+C to quit
117
+ 2025-11-21 17:34:01,155 - * Restarting with stat
118
+ 2025-11-21 17:34:14,790 - * Debugger is active!
119
+ 2025-11-21 17:34:14,795 - * Debugger PIN: 465-628-698
120
+ 2025-11-21 17:34:36,971 - 127.0.0.1 - - [21/Nov/2025 17:34:36] "GET / HTTP/1.1" 200 -
121
+ 2025-11-21 17:34:37,154 - 127.0.0.1 - - [21/Nov/2025 17:34:37] "GET /static/site.webmanifest HTTP/1.1" 304 -
122
+ 2025-11-21 17:34:51,570 - 127.0.0.1 - - [21/Nov/2025 17:34:51] "POST /transcribe HTTP/1.1" 500 -
123
+ 2025-11-21 17:34:54,847 - 127.0.0.1 - - [21/Nov/2025 17:34:54] "POST /transcribe HTTP/1.1" 500 -
124
+ 2025-11-21 17:35:02,069 - 127.0.0.1 - - [21/Nov/2025 17:35:02] "POST /transcribe HTTP/1.1" 500 -
125
+ 2025-11-21 17:35:16,704 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
126
+ 2025-11-21 17:35:17,645 - * Restarting with stat
127
+ 2025-11-21 17:35:31,749 - * Debugger is active!
128
+ 2025-11-21 17:35:31,753 - * Debugger PIN: 465-628-698
129
+ 2025-11-21 17:36:46,964 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
130
+ 2025-11-21 17:36:47,930 - * Restarting with stat
131
+ 2025-11-21 17:37:02,683 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
132
+ * Running on http://127.0.0.1:5000
133
+ 2025-11-21 17:37:02,683 - Press CTRL+C to quit
134
+ 2025-11-21 17:37:02,687 - * Restarting with stat
135
+ 2025-11-21 17:37:15,615 - * Debugger is active!
136
+ 2025-11-21 17:37:15,617 - * Debugger PIN: 465-628-698
137
+ 2025-11-21 17:37:15,714 - 127.0.0.1 - - [21/Nov/2025 17:37:15] "GET / HTTP/1.1" 200 -
138
+ 2025-11-21 17:37:15,841 - 127.0.0.1 - - [21/Nov/2025 17:37:15] "GET /static/site.webmanifest HTTP/1.1" 304 -
139
+ 2025-11-21 17:37:30,830 - 127.0.0.1 - - [21/Nov/2025 17:37:30] "POST /transcribe HTTP/1.1" 200 -
140
+ 2025-11-21 17:37:44,136 - 127.0.0.1 - - [21/Nov/2025 17:37:44] "POST /transcribe HTTP/1.1" 200 -
141
+ 2025-11-21 17:38:01,648 - 127.0.0.1 - - [21/Nov/2025 17:38:01] "POST /transcribe HTTP/1.1" 200 -
static/android-chrome-192x192.png ADDED
static/android-chrome-512x512.png ADDED

Git LFS Details

  • SHA256: b171ec2611e5755bf1fc228568dcc8568d93879a874f0fc9c3c5fdec692db697
  • Pointer size: 131 Bytes
  • Size of remote file: 222 kB
static/apple-touch-icon.png ADDED
static/favicon-16x16.png ADDED
static/favicon-32x32.png ADDED
static/favicon.ico ADDED
static/site.webmanifest ADDED
@@ -0,0 +1 @@
 
 
1
+ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
templates/index.html ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Saayam AI Assistant</title>
6
+
7
+ <!-- Favicon & Icons -->
8
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}">
9
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon-32x32.png') }}">
10
+ <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon-16x16.png') }}">
11
+ <link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}">
12
+ <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
13
+
14
+ <!-- Add marked.js for Markdown rendering -->
15
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
16
+
17
+ <style>
18
+ body {
19
+ font-family: Arial, sans-serif;
20
+ margin: 20px;
21
+ background-color: #f9f9f9;
22
+ }
23
+ .container {
24
+ max-width: 600px;
25
+ margin: auto;
26
+ background: #fff;
27
+ padding: 20px;
28
+ border-radius: 8px;
29
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
30
+ }
31
+ h1 {
32
+ text-align: center;
33
+ color: #4CAF50;
34
+ }
35
+ label {
36
+ font-weight: bold;
37
+ margin-top: 10px;
38
+ display: block;
39
+ }
40
+ textarea, select, input, button {
41
+ margin-top: 10px;
42
+ width: 100%;
43
+ padding: 10px;
44
+ border: 1px solid #ccc;
45
+ border-radius: 5px;
46
+ box-sizing: border-box; /* Ensures padding doesn't affect width */
47
+ }
48
+ button {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ border: none;
52
+ cursor: pointer;
53
+ font-size: 16px;
54
+ }
55
+ button:hover {
56
+ background-color: #45a049;
57
+ }
58
+
59
+ /* STT Toolbar Styles */
60
+ .stt-toolbar {
61
+ display: flex;
62
+ gap: 10px;
63
+ margin-top: 5px;
64
+ margin-bottom: 5px;
65
+ align-items: center;
66
+ }
67
+ .stt-select {
68
+ flex-grow: 1;
69
+ margin-top: 0;
70
+ padding: 8px;
71
+ }
72
+ .mic-button {
73
+ width: auto;
74
+ margin-top: 0;
75
+ background-color: #f0f0f0;
76
+ color: #333;
77
+ border: 1px solid #ccc;
78
+ padding: 8px 15px;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 5px;
82
+ transition: all 0.3s ease;
83
+ }
84
+ .mic-button:hover {
85
+ background-color: #e0e0e0;
86
+ }
87
+ .mic-button.recording {
88
+ background-color: #ffebee;
89
+ color: #d32f2f;
90
+ border-color: #d32f2f;
91
+ animation: pulse 1.5s infinite;
92
+ }
93
+ .mic-icon {
94
+ width: 16px;
95
+ height: 16px;
96
+ fill: currentColor;
97
+ }
98
+ @keyframes pulse {
99
+ 0% { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0.4); }
100
+ 70% { box-shadow: 0 0 0 10px rgba(211, 47, 47, 0); }
101
+ 100% { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0); }
102
+ }
103
+
104
+ .predicted-categories {
105
+ margin-top: 10px;
106
+ display: flex;
107
+ gap: 10px;
108
+ flex-wrap: wrap;
109
+ }
110
+ .category-option {
111
+ padding: 10px;
112
+ border: 1px solid #ccc;
113
+ border-radius: 5px;
114
+ background-color: #e7f5e7;
115
+ cursor: pointer;
116
+ }
117
+ .category-option.selected {
118
+ background-color: #4CAF50;
119
+ color: white;
120
+ border: 1px solid #4CAF50;
121
+ }
122
+ .loading-message {
123
+ text-align: center;
124
+ margin-top: 10px;
125
+ font-style: italic;
126
+ color: #888;
127
+ }
128
+ .response {
129
+ margin-top: 20px;
130
+ display: none;
131
+ }
132
+ .response p {
133
+ background: #e7f5e7;
134
+ padding: 10px;
135
+ border: 1px solid #4CAF50;
136
+ border-radius: 5px;
137
+ font-family: Arial, sans-serif;
138
+ }
139
+ .metrics {
140
+ margin-top: 10px;
141
+ padding: 10px;
142
+ background: #f0f0f0;
143
+ border-radius: 5px;
144
+ }
145
+ .metrics p {
146
+ margin: 5px 0;
147
+ }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <div class="container">
152
+ <h1>Saayam AI Assistant</h1>
153
+ <p><strong>Homepage:</strong><br>Enter a subject and description for your query.<br>
154
+ Optionally, select a category from the dropdown menu.</p>
155
+
156
+ <form id="qa-form">
157
+ <label for="subject">Subject:</label>
158
+ <input id="subject" name="subject" type="text" required />
159
+
160
+ <label for="category">Category (Optional):</label>
161
+ <select id="category" name="category">
162
+ <option value="" disabled selected>Select a category</option>
163
+ </select>
164
+
165
+ <label for="description">Description:</label>
166
+
167
+ <!-- STT Toolbar -->
168
+ <div class="stt-toolbar">
169
+ <select id="stt-language" class="stt-select">
170
+ <option value="auto">Auto-Detect Language</option>
171
+ <option value="en">English</option>
172
+ <option value="zh">Mandarin Chinese</option>
173
+ <option value="hi">Hindi</option>
174
+ <option value="es">Spanish</option>
175
+ <option value="fr">French</option>
176
+ <option value="ar">Arabic</option>
177
+ <option value="bn">Bengali</option>
178
+ <option value="pt">Portuguese</option>
179
+ <option value="ru">Russian</option>
180
+ <option value="ur">Urdu</option>
181
+ <option value="id">Indonesian</option>
182
+ <option value="de">German</option>
183
+ <option value="ja">Japanese</option>
184
+ </select>
185
+ <button type="button" id="mic-btn" class="mic-button" onclick="toggleRecording()">
186
+ <svg class="mic-icon" viewBox="0 0 24 24">
187
+ <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
188
+ <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
189
+ </svg>
190
+ Record
191
+ </button>
192
+ </div>
193
+
194
+ <textarea id="description" name="description" rows="4" required placeholder="Type or use the microphone to speak..."></textarea>
195
+
196
+ <div id="category-prediction-area"></div>
197
+
198
+ <button type="button" onclick="askQuestion()" style="margin-top: 20px;">Ask</button>
199
+ </form>
200
+
201
+ <div class="response" id="response-container">
202
+ <h3>AI Response:</h3>
203
+ <div id="response-text"></div>
204
+ <div class="metrics" id="metrics-container"></div>
205
+ </div>
206
+ </div>
207
+
208
+ <script>
209
+ const categories = JSON.parse('{{ categories | tojson | safe }}');
210
+ const categoryDropdown = document.getElementById("category");
211
+ const predictionArea = document.getElementById("category-prediction-area");
212
+ let selectedCategory = "";
213
+
214
+ // Populate category dropdown
215
+ categories.forEach(cat => {
216
+ const option = document.createElement("option");
217
+ option.value = cat;
218
+ option.textContent = cat;
219
+ categoryDropdown.appendChild(option);
220
+ });
221
+
222
+ // --- Speech to Text Logic ---
223
+ let mediaRecorder;
224
+ let audioChunks = [];
225
+ const micBtn = document.getElementById('mic-btn');
226
+ const descriptionArea = document.getElementById('description');
227
+
228
+ async function toggleRecording() {
229
+ if (mediaRecorder && mediaRecorder.state === "recording") {
230
+ mediaRecorder.stop();
231
+ } else {
232
+ try {
233
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
234
+ startRecording(stream);
235
+ } catch (err) {
236
+ console.error("Error accessing microphone:", err);
237
+ alert("Microphone access denied or unavailable.");
238
+ }
239
+ }
240
+ }
241
+
242
+ function startRecording(stream) {
243
+ audioChunks = [];
244
+ let options;
245
+ if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {
246
+ options = { mimeType: 'audio/webm;codecs=opus' };
247
+ } else if (MediaRecorder.isTypeSupported('audio/webm')) {
248
+ options = { mimeType: 'audio/webm' };
249
+ } else {
250
+ options = { mimeType: 'audio/ogg;codecs=opus' };
251
+ }
252
+ mediaRecorder = new MediaRecorder(stream, options);
253
+
254
+ mediaRecorder.ondataavailable = event => {
255
+ audioChunks.push(event.data);
256
+ };
257
+
258
+ mediaRecorder.onstop = async () => {
259
+ // Visual reset
260
+ micBtn.classList.remove("recording");
261
+ micBtn.innerHTML = `
262
+ <svg class="mic-icon" viewBox="0 0 24 24">
263
+ <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
264
+ <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
265
+ </svg> Record`;
266
+ descriptionArea.placeholder = "Processing audio...";
267
+
268
+ // Prepare file
269
+ const mimeType = mediaRecorder.mimeType || 'audio/webm';
270
+ const audioBlob = new Blob(audioChunks, { type: mimeType });
271
+ const formData = new FormData();
272
+ formData.append("audio", audioBlob);
273
+
274
+ const lang = document.getElementById('stt-language').value;
275
+ formData.append("language", lang);
276
+
277
+ try {
278
+ const response = await fetch('/transcribe', {
279
+ method: 'POST',
280
+ body: formData
281
+ });
282
+
283
+ const data = await response.json();
284
+
285
+ if (data.text) {
286
+ // Append text if content exists, otherwise set it
287
+ const currentText = descriptionArea.value;
288
+ descriptionArea.value = currentText ? currentText + " " + data.text : data.text;
289
+ descriptionArea.placeholder = "Type or use the microphone to speak...";
290
+ } else if (data.error) {
291
+ alert("Transcription failed: " + data.error);
292
+ descriptionArea.placeholder = "Error in transcription.";
293
+ }
294
+ } catch (error) {
295
+ console.error("API Error:", error);
296
+ alert("Failed to connect to transcription server.");
297
+ descriptionArea.placeholder = "Connection failed.";
298
+ }
299
+
300
+ // Stop all tracks to release mic
301
+ stream.getTracks().forEach(track => track.stop());
302
+ };
303
+
304
+ mediaRecorder.start();
305
+ micBtn.classList.add("recording");
306
+ micBtn.innerHTML = "Listening... (Click to Stop)";
307
+ descriptionArea.placeholder = "Listening...";
308
+ }
309
+
310
+ // --- Existing Logic ---
311
+
312
+ function askQuestion() {
313
+ const subject = document.getElementById("subject").value;
314
+ const category = document.getElementById("category").value || selectedCategory;
315
+ const description = document.getElementById("description").value;
316
+
317
+ if (!subject || !description) {
318
+ alert("Please fill out all required fields.");
319
+ return;
320
+ }
321
+
322
+ // Basic prediction logic if category missing
323
+ if (!category) {
324
+ predictionArea.innerHTML = '<p class="loading-message">Predicting categories...</p>';
325
+ fetch('/predict_categories', {
326
+ method: 'POST',
327
+ headers: { 'Content-Type': 'application/json' },
328
+ body: JSON.stringify({ subject, description })
329
+ })
330
+ .then(res => res.json())
331
+ .then(data => {
332
+ predictionArea.innerHTML = "";
333
+ const predictedContainer = document.createElement("div");
334
+ predictedContainer.className = "predicted-categories";
335
+
336
+ if(data.predicted_categories) {
337
+ data.predicted_categories.forEach(cat => {
338
+ const div = document.createElement("div");
339
+ div.textContent = cat;
340
+ div.className = "category-option";
341
+ div.onclick = () => selectCategory(div, cat);
342
+ predictedContainer.appendChild(div);
343
+ });
344
+ }
345
+ predictionArea.appendChild(predictedContainer);
346
+ })
347
+ .catch(err => {
348
+ predictionArea.innerHTML = "<p class='loading-message'>Failed to predict categories.</p>";
349
+ console.error(err);
350
+ });
351
+ return;
352
+ }
353
+
354
+ const responseContainer = document.getElementById("response-container");
355
+ const responseText = document.getElementById("response-text");
356
+ responseContainer.style.display = 'block';
357
+ responseText.innerHTML = '<p class="loading-message">Thinking...</p>';
358
+ document.getElementById("metrics-container").innerHTML = "";
359
+
360
+ fetch('/generate_answer', {
361
+ method: 'POST',
362
+ headers: { 'Content-Type': 'application/json' },
363
+ body: JSON.stringify({ category, question: description })
364
+ })
365
+ .then(res => res.json())
366
+ .then(data => {
367
+ if (data.error) {
368
+ responseText.innerHTML = `<p style="color:red">Error: ${data.error}</p>`;
369
+ return;
370
+ }
371
+
372
+ // Render the response as Markdown
373
+ responseText.innerHTML = marked.parse(data.answer);
374
+
375
+ // Display metrics
376
+ const metricsContainer = document.getElementById("metrics-container");
377
+ if (data.metrics) {
378
+ metricsContainer.innerHTML = `
379
+ <h4>Performance Metrics:</h4>
380
+ <p><strong>Model:</strong> ${data.metrics.model}</p>
381
+ <p><strong>Temperature:</strong> ${data.metrics.temperature}</p>
382
+ <p><strong>Time to First Token (TTFT):</strong> ${data.metrics.ttft_seconds} seconds</p>
383
+ <p><strong>Total Response Time (TTLT):</strong> ${data.metrics.ttlt_seconds} seconds</p>
384
+ <p><strong>Speed:</strong> ${data.metrics.speed_tokens_per_second} tokens/second</p>
385
+ <p><strong>Input Tokens:</strong> ${data.metrics.input_tokens}</p>
386
+ <p><strong>Output Tokens:</strong> ${data.metrics.output_tokens}</p>
387
+ `;
388
+ } else {
389
+ metricsContainer.innerHTML = '<p>No metrics available.</p>';
390
+ }
391
+ })
392
+ .catch(err => {
393
+ console.error(err);
394
+ responseText.innerHTML = `<p style="color:red">Request failed.</p>`;
395
+ });
396
+ }
397
+
398
+ function selectCategory(element, category) {
399
+ document.querySelectorAll(".category-option").forEach(el => el.classList.remove("selected"));
400
+ element.classList.add("selected");
401
+ selectedCategory = category;
402
+ }
403
+ </script>
404
+ </body>
405
+ </html>
test_app.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from app import app, categories
3
+
4
+ @pytest.fixture
5
+ def client():
6
+ with app.test_client() as client:
7
+ yield client
8
+
9
+ def test_homepage(client):
10
+ response = client.get('/')
11
+ assert response.status_code == 200
12
+ assert b"Saayam AI Assistant" in response.data
13
+
14
+ def test_predict_categories(client):
15
+ response = client.post('/predict_categories', json={
16
+ "subject": "How to save money?",
17
+ "description": "I want to start budgeting my expenses."
18
+ })
19
+ assert response.status_code == 200
20
+ data = response.get_json()
21
+ assert "predicted_categories" in data
22
+ assert len(data["predicted_categories"]) > 0
23
+
24
+ @pytest.mark.parametrize("category", categories[:5]) # Use a few categories for demo
25
+ def test_generate_answer(client, category):
26
+ response = client.post('/generate_answer', json={
27
+ "category": category,
28
+ "question": "Tell me something about this category."
29
+ })
30
+ assert response.status_code == 200
31
+ data = response.get_json()
32
+ assert "answer" in data or "error" in data