jaiarora123 commited on
Commit
455aeb6
Β·
verified Β·
1 Parent(s): 40520df

Update backend.py

Browse files
Files changed (1) hide show
  1. backend.py +373 -370
backend.py CHANGED
@@ -1,371 +1,374 @@
1
- """
2
- Meeting Minutes Generator - Backend API
3
- Handles audio transcription and minutes generation using Groq
4
- """
5
-
6
- # ============================================
7
- # IMPORTS
8
- # ============================================
9
- from fastapi import FastAPI, File, UploadFile, HTTPException
10
- from pydantic import BaseModel
11
- from typing import Optional
12
- from groq import Groq
13
- import os
14
- from dotenv import load_dotenv
15
-
16
- # ============================================
17
- # LOAD ENVIRONMENT VARIABLES
18
- # ============================================
19
- load_dotenv() # Reads .env file and loads variables
20
-
21
- # ============================================
22
- # INITIALIZE FASTAPI APP
23
- # ============================================
24
- app = FastAPI(
25
- title="Meeting Minutes API",
26
- version="2.0.0",
27
- description="Transcribe meeting audio and generate formatted minutes using Groq"
28
- )
29
-
30
- # ============================================
31
- # INITIALIZE GROQ CLIENT
32
- # ============================================
33
- # Get API key from environment
34
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
35
-
36
- # Validate API key exists
37
- if not GROQ_API_KEY:
38
- raise ValueError("❌ GROQ_API_KEY not found in environment. Check your .env file!")
39
-
40
- # Create Groq client
41
- groq_client = Groq(api_key=GROQ_API_KEY)
42
-
43
- # ============================================
44
- # PYDANTIC MODELS (Type Safety & Documentation)
45
- # ============================================
46
-
47
- class TranscribeResponse(BaseModel):
48
- """
49
- Response model for successful transcription
50
- """
51
- transcript: str # The transcribed text
52
- file_size_mb: float # Size of uploaded file in MB
53
- filename: str # Original filename
54
- success: bool # Always True for successful responses
55
-
56
- class GenerateMinutesRequest(BaseModel):
57
- """
58
- Request model for generating minutes
59
- """
60
- transcript: str # Raw transcript text from /transcribe
61
-
62
- class GenerateMinutesResponse(BaseModel):
63
- """
64
- Response model for generated minutes
65
- """
66
- minutes: str # Formatted Markdown minutes
67
- success: bool # Always True for successful responses
68
-
69
- class ErrorResponse(BaseModel):
70
- """
71
- Response model for errors
72
- """
73
- error: str # Error message
74
- detail: Optional[str] # Additional error details
75
-
76
- # ============================================
77
- # CONSTANTS
78
- # ============================================
79
-
80
- # Transcription settings
81
- MAX_FILE_SIZE_MB = 25 # Groq Whisper limit
82
- WHISPER_MODEL = "whisper-large-v3" # Most accurate Whisper model
83
- WHISPER_TEMPERATURE = 0.1 # Slight randomness for better transcription
84
-
85
- # Minutes generation settings
86
- LLM_MODEL = "openai/gpt-oss-120b" # Groq LLM model for minutes
87
- LLM_TEMPERATURE = 0.1 # Low temperature for consistent, factual output
88
- MAX_COMPLETION_TOKENS = 1024 # Enough for any realistic meeting minutes
89
-
90
- # System prompt for minutes generation
91
- MINUTES_SYSTEM_PROMPT = """You are an assistant that converts meeting transcripts into concise, factual minutes.
92
-
93
- Your task:
94
- 1. Remove filler words and disfluencies (uh, um, like, you know).
95
- 2. Restore punctuation and sentence boundaries.
96
- 3. Extract clear, factual minutes.
97
- 4. Do NOT invent facts. If unclear, mark [unclear].
98
- 5. Return the final output strictly in Markdown format with headings, bullets, and bold labels.
99
-
100
- Use this exact structure:
101
-
102
- ## **Minutes of the Meeting**
103
- **- Date:** [if present, else "Unknown"]
104
- **- Attendees:** [if not mentioned, say "Unknown"; if only able to recognise a few, write their names and say "and others"]
105
-
106
- ### **Summary**
107
- [2–3 sentences summarizing purpose and tone]
108
-
109
- ### **Key Agenda and Discussions**
110
- 1. ...
111
- 2. ...
112
-
113
- ### **Action Items**
114
- 1. ...
115
- 2. ...
116
-
117
- ### **Open Issues / Concerns**
118
- 1. ...
119
- 2. ...
120
-
121
- ### **Notes** [minimum 3 sentences]
122
- - Short factual notes or clarifications.
123
-
124
- Be concise, professional, and factually grounded. Maintain Markdown formatting faithfully."""
125
-
126
- # ============================================
127
- # HELPER FUNCTIONS
128
- # ============================================
129
-
130
- def check_file_size(file_bytes: bytes) -> tuple[bool, float]:
131
- """
132
- Check if uploaded file is within size limit
133
-
134
- Args:
135
- file_bytes: Raw file bytes
136
-
137
- Returns:
138
- tuple: (is_valid, size_in_mb)
139
- - is_valid: True if file is under limit
140
- - size_in_mb: Actual file size in megabytes
141
- """
142
- size_mb = len(file_bytes) / (1024 * 1024) # Convert bytes to MB
143
- is_valid = size_mb <= MAX_FILE_SIZE_MB
144
- return is_valid, size_mb
145
-
146
- # ============================================
147
- # API ENDPOINTS
148
- # ============================================
149
-
150
- @app.get("/")
151
- def root():
152
- """
153
- Health check endpoint
154
-
155
- Returns API status and version info
156
- Used to verify backend is running correctly
157
- """
158
- return {
159
- "message": "πŸŽ™οΈ Meeting Minutes API is running!",
160
- "version": "2.0.0",
161
- "status": "healthy",
162
- "endpoints": {
163
- "transcribe": "/transcribe (POST)",
164
- "generate_minutes": "/generate-minutes (POST)",
165
- "health": "/ (GET)"
166
- }
167
- }
168
-
169
- @app.post("/transcribe", response_model=TranscribeResponse)
170
- async def transcribe_audio(file: UploadFile = File(...)):
171
- """
172
- Transcribe audio file to text using Groq Whisper Large v3
173
-
174
- FLOW:
175
- 1. Receive audio file from client (Gradio UI)
176
- 2. Read file bytes into memory
177
- 3. Validate file size (must be < 25MB)
178
- 4. Send file to Groq Whisper API
179
- 5. Receive transcript text
180
- 6. Validate transcript is not empty
181
- 7. Return transcript with metadata
182
-
183
- Args:
184
- file: Uploaded audio file
185
- Supported formats: mp3, wav, m4a, webm, flac
186
-
187
- Returns:
188
- TranscribeResponse: Contains transcript text and metadata
189
-
190
- Raises:
191
- HTTPException 400: File too large or invalid
192
- HTTPException 500: Groq API error
193
- """
194
-
195
- # ========================================
196
- # STEP 1: Read uploaded file bytes
197
- # ========================================
198
- try:
199
- file_bytes = await file.read()
200
- except Exception as e:
201
- raise HTTPException(
202
- status_code=400,
203
- detail=f"Failed to read uploaded file: {str(e)}"
204
- )
205
-
206
- # ========================================
207
- # STEP 2: Validate file size
208
- # ========================================
209
- is_valid_size, size_mb = check_file_size(file_bytes)
210
-
211
- if not is_valid_size:
212
- raise HTTPException(
213
- status_code=400,
214
- detail=f"File too large ({size_mb:.2f}MB). Maximum allowed is {MAX_FILE_SIZE_MB}MB. "
215
- f"Please upload a shorter recording or compress the audio."
216
- )
217
-
218
- # ========================================
219
- # STEP 3: Call Groq Whisper API
220
- # ========================================
221
- try:
222
- # Create transcription request
223
- # Note: file parameter expects tuple of (filename, bytes)
224
- transcription = groq_client.audio.transcriptions.create(
225
- file=(file.filename, file_bytes), # Tuple: (name, bytes)
226
- model=WHISPER_MODEL, # whisper-large-v3
227
- temperature=WHISPER_TEMPERATURE, # 0.1 for slightly varied but consistent output
228
- response_format="text" # Returns plain text (not JSON)
229
- )
230
-
231
- # Extract transcript text from response
232
- # When response_format="text", the response IS the text string
233
- transcript_text = transcription
234
-
235
- except Exception as e:
236
- # Catch any Groq API errors (rate limits, network issues, etc.)
237
- raise HTTPException(
238
- status_code=500,
239
- detail=f"Transcription failed: {str(e)}. Please try again."
240
- )
241
-
242
- # ========================================
243
- # STEP 4: Validate transcript is not empty
244
- # ========================================
245
- if not transcript_text or len(transcript_text.strip()) == 0:
246
- raise HTTPException(
247
- status_code=400,
248
- detail="No speech detected in audio file. Please ensure the recording contains clear speech."
249
- )
250
-
251
- # ========================================
252
- # STEP 5: Return successful response
253
- # ========================================
254
- return TranscribeResponse(
255
- transcript=transcript_text.strip(), # Remove leading/trailing whitespace
256
- file_size_mb=round(size_mb, 2), # Round to 2 decimal places
257
- filename=file.filename, # Original filename
258
- success=True # Success flag
259
- )
260
-
261
- @app.post("/generate-minutes", response_model=GenerateMinutesResponse)
262
- async def generate_minutes(request: GenerateMinutesRequest):
263
- """
264
- Generate formatted meeting minutes from raw transcript using Groq LLM
265
-
266
- FLOW:
267
- 1. Receive raw transcript text
268
- 2. Validate transcript is not empty
269
- 3. Build messages array (system prompt + user transcript)
270
- 4. Call Groq LLM (gpt-oss-120b)
271
- 5. Receive formatted Markdown minutes
272
- 6. Validate output is not empty
273
- 7. Return formatted minutes
274
-
275
- Args:
276
- request: GenerateMinutesRequest containing transcript text
277
-
278
- Returns:
279
- GenerateMinutesResponse: Contains formatted Markdown minutes
280
-
281
- Raises:
282
- HTTPException 400: Empty transcript
283
- HTTPException 500: Groq API error
284
- """
285
-
286
- # ========================================
287
- # STEP 1: Validate transcript is not empty
288
- # ========================================
289
- if not request.transcript or len(request.transcript.strip()) == 0:
290
- raise HTTPException(
291
- status_code=400,
292
- detail="Transcript cannot be empty. Please provide a valid transcript."
293
- )
294
-
295
- # ========================================
296
- # STEP 2: Build messages array for LLM
297
- # ========================================
298
- messages = [
299
- {
300
- "role": "system",
301
- "content": MINUTES_SYSTEM_PROMPT
302
- },
303
- {
304
- "role": "user",
305
- "content": f"Please convert the following meeting transcript into structured minutes:\n\n{request.transcript}"
306
- }
307
- ]
308
-
309
- # ========================================
310
- # STEP 3: Call Groq LLM API
311
- # ========================================
312
- try:
313
- # Create chat completion request
314
- completion = groq_client.chat.completions.create(
315
- model=LLM_MODEL, # openai/gpt-oss-120b
316
- messages=messages, # System prompt + user transcript
317
- temperature=LLM_TEMPERATURE, # 0.1 for deterministic output
318
- max_completion_tokens=MAX_COMPLETION_TOKENS, # 1024 tokens max
319
- top_p=1, # Standard sampling
320
- stream=False, # Get complete response at once
321
- stop=None # No custom stop sequences
322
- )
323
-
324
- # Extract generated minutes from response
325
- minutes_text = completion.choices[0].message.content
326
-
327
- except Exception as e:
328
- # Catch any Groq API errors (rate limits, network issues, etc.)
329
- raise HTTPException(
330
- status_code=500,
331
- detail=f"Minutes generation failed: {str(e)}. Please try again."
332
- )
333
-
334
- # ========================================
335
- # STEP 4: Validate minutes are not empty
336
- # ========================================
337
- if not minutes_text or len(minutes_text.strip()) == 0:
338
- raise HTTPException(
339
- status_code=500,
340
- detail="LLM returned empty response. Please try again."
341
- )
342
-
343
- # ========================================
344
- # STEP 5: Return successful response
345
- # ========================================
346
- return GenerateMinutesResponse(
347
- minutes=minutes_text.strip(), # Remove leading/trailing whitespace
348
- success=True # Success flag
349
- )
350
-
351
- # ============================================
352
- # RUN SERVER (for local testing)
353
- # ============================================
354
- if __name__ == "__main__":
355
- import uvicorn
356
-
357
- print("πŸš€ Starting Meeting Minutes Backend...")
358
- print("πŸ“ Server will run on: http://localhost:8000")
359
- print("πŸ“– API docs available at: http://localhost:8000/docs")
360
- print("πŸ” Health check: http://localhost:8000")
361
- print("\nβœ… Available endpoints:")
362
- print(" POST /transcribe - Convert audio to text")
363
- print(" POST /generate-minutes - Convert transcript to formatted minutes")
364
-
365
- # Run the FastAPI app with uvicorn
366
- uvicorn.run(
367
- app,
368
- host="0.0.0.0", # Listen on all network interfaces
369
- port=8001, # Port 8000 (standard for APIs)
370
- log_level="info" # Show request logs
 
 
 
371
  )
 
1
+ """
2
+ Meeting Minutes Generator - Backend API
3
+ Handles audio transcription and minutes generation using Groq
4
+ """
5
+
6
+ # ============================================
7
+ # IMPORTS
8
+ # ============================================
9
+ from fastapi import FastAPI, File, UploadFile, HTTPException
10
+ from pydantic import BaseModel
11
+ from typing import Optional
12
+ from groq import Groq
13
+ import os
14
+ from dotenv import load_dotenv
15
+
16
+ # ============================================
17
+ # LOAD ENVIRONMENT VARIABLES
18
+ # ============================================
19
+ load_dotenv() # Reads .env file and loads variables
20
+
21
+ # ============================================
22
+ # INITIALIZE FASTAPI APP
23
+ # ============================================
24
+ app = FastAPI(
25
+ title="Meeting Minutes API",
26
+ version="2.0.0",
27
+ description="Transcribe meeting audio and generate formatted minutes using Groq"
28
+ )
29
+
30
+ # ============================================
31
+ # INITIALIZE GROQ CLIENT
32
+ # ============================================
33
+ # Get API key from environment
34
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
35
+
36
+ # Validate API key exists
37
+ if not GROQ_API_KEY:
38
+ raise ValueError("❌ GROQ_API_KEY not found in environment. Check your .env file!")
39
+
40
+ # Create Groq client
41
+ groq_client = Groq(
42
+ api_key=GROQ_API_KEY,
43
+ max_retries=2,
44
+ timeout=120.0
45
+ )
46
+ # ============================================
47
+ # PYDANTIC MODELS (Type Safety & Documentation)
48
+ # ============================================
49
+
50
+ class TranscribeResponse(BaseModel):
51
+ """
52
+ Response model for successful transcription
53
+ """
54
+ transcript: str # The transcribed text
55
+ file_size_mb: float # Size of uploaded file in MB
56
+ filename: str # Original filename
57
+ success: bool # Always True for successful responses
58
+
59
+ class GenerateMinutesRequest(BaseModel):
60
+ """
61
+ Request model for generating minutes
62
+ """
63
+ transcript: str # Raw transcript text from /transcribe
64
+
65
+ class GenerateMinutesResponse(BaseModel):
66
+ """
67
+ Response model for generated minutes
68
+ """
69
+ minutes: str # Formatted Markdown minutes
70
+ success: bool # Always True for successful responses
71
+
72
+ class ErrorResponse(BaseModel):
73
+ """
74
+ Response model for errors
75
+ """
76
+ error: str # Error message
77
+ detail: Optional[str] # Additional error details
78
+
79
+ # ============================================
80
+ # CONSTANTS
81
+ # ============================================
82
+
83
+ # Transcription settings
84
+ MAX_FILE_SIZE_MB = 25 # Groq Whisper limit
85
+ WHISPER_MODEL = "whisper-large-v3" # Most accurate Whisper model
86
+ WHISPER_TEMPERATURE = 0.1 # Slight randomness for better transcription
87
+
88
+ # Minutes generation settings
89
+ LLM_MODEL = "openai/gpt-oss-120b" # Groq LLM model for minutes
90
+ LLM_TEMPERATURE = 0.1 # Low temperature for consistent, factual output
91
+ MAX_COMPLETION_TOKENS = 1024 # Enough for any realistic meeting minutes
92
+
93
+ # System prompt for minutes generation
94
+ MINUTES_SYSTEM_PROMPT = """You are an assistant that converts meeting transcripts into concise, factual minutes.
95
+
96
+ Your task:
97
+ 1. Remove filler words and disfluencies (uh, um, like, you know).
98
+ 2. Restore punctuation and sentence boundaries.
99
+ 3. Extract clear, factual minutes.
100
+ 4. Do NOT invent facts. If unclear, mark [unclear].
101
+ 5. Return the final output strictly in Markdown format with headings, bullets, and bold labels.
102
+
103
+ Use this exact structure:
104
+
105
+ ## **Minutes of the Meeting**
106
+ **- Date:** [if present, else "Unknown"]
107
+ **- Attendees:** [if not mentioned, say "Unknown"; if only able to recognise a few, write their names and say "and others"]
108
+
109
+ ### **Summary**
110
+ [2–3 sentences summarizing purpose and tone]
111
+
112
+ ### **Key Agenda and Discussions**
113
+ 1. ...
114
+ 2. ...
115
+
116
+ ### **Action Items**
117
+ 1. ...
118
+ 2. ...
119
+
120
+ ### **Open Issues / Concerns**
121
+ 1. ...
122
+ 2. ...
123
+
124
+ ### **Notes** [minimum 3 sentences]
125
+ - Short factual notes or clarifications.
126
+
127
+ Be concise, professional, and factually grounded. Maintain Markdown formatting faithfully."""
128
+
129
+ # ============================================
130
+ # HELPER FUNCTIONS
131
+ # ============================================
132
+
133
+ def check_file_size(file_bytes: bytes) -> tuple[bool, float]:
134
+ """
135
+ Check if uploaded file is within size limit
136
+
137
+ Args:
138
+ file_bytes: Raw file bytes
139
+
140
+ Returns:
141
+ tuple: (is_valid, size_in_mb)
142
+ - is_valid: True if file is under limit
143
+ - size_in_mb: Actual file size in megabytes
144
+ """
145
+ size_mb = len(file_bytes) / (1024 * 1024) # Convert bytes to MB
146
+ is_valid = size_mb <= MAX_FILE_SIZE_MB
147
+ return is_valid, size_mb
148
+
149
+ # ============================================
150
+ # API ENDPOINTS
151
+ # ============================================
152
+
153
+ @app.get("/")
154
+ def root():
155
+ """
156
+ Health check endpoint
157
+
158
+ Returns API status and version info
159
+ Used to verify backend is running correctly
160
+ """
161
+ return {
162
+ "message": "πŸŽ™οΈ Meeting Minutes API is running!",
163
+ "version": "2.0.0",
164
+ "status": "healthy",
165
+ "endpoints": {
166
+ "transcribe": "/transcribe (POST)",
167
+ "generate_minutes": "/generate-minutes (POST)",
168
+ "health": "/ (GET)"
169
+ }
170
+ }
171
+
172
+ @app.post("/transcribe", response_model=TranscribeResponse)
173
+ async def transcribe_audio(file: UploadFile = File(...)):
174
+ """
175
+ Transcribe audio file to text using Groq Whisper Large v3
176
+
177
+ FLOW:
178
+ 1. Receive audio file from client (Gradio UI)
179
+ 2. Read file bytes into memory
180
+ 3. Validate file size (must be < 25MB)
181
+ 4. Send file to Groq Whisper API
182
+ 5. Receive transcript text
183
+ 6. Validate transcript is not empty
184
+ 7. Return transcript with metadata
185
+
186
+ Args:
187
+ file: Uploaded audio file
188
+ Supported formats: mp3, wav, m4a, webm, flac
189
+
190
+ Returns:
191
+ TranscribeResponse: Contains transcript text and metadata
192
+
193
+ Raises:
194
+ HTTPException 400: File too large or invalid
195
+ HTTPException 500: Groq API error
196
+ """
197
+
198
+ # ========================================
199
+ # STEP 1: Read uploaded file bytes
200
+ # ========================================
201
+ try:
202
+ file_bytes = await file.read()
203
+ except Exception as e:
204
+ raise HTTPException(
205
+ status_code=400,
206
+ detail=f"Failed to read uploaded file: {str(e)}"
207
+ )
208
+
209
+ # ========================================
210
+ # STEP 2: Validate file size
211
+ # ========================================
212
+ is_valid_size, size_mb = check_file_size(file_bytes)
213
+
214
+ if not is_valid_size:
215
+ raise HTTPException(
216
+ status_code=400,
217
+ detail=f"File too large ({size_mb:.2f}MB). Maximum allowed is {MAX_FILE_SIZE_MB}MB. "
218
+ f"Please upload a shorter recording or compress the audio."
219
+ )
220
+
221
+ # ========================================
222
+ # STEP 3: Call Groq Whisper API
223
+ # ========================================
224
+ try:
225
+ # Create transcription request
226
+ # Note: file parameter expects tuple of (filename, bytes)
227
+ transcription = groq_client.audio.transcriptions.create(
228
+ file=(file.filename, file_bytes), # Tuple: (name, bytes)
229
+ model=WHISPER_MODEL, # whisper-large-v3
230
+ temperature=WHISPER_TEMPERATURE, # 0.1 for slightly varied but consistent output
231
+ response_format="text" # Returns plain text (not JSON)
232
+ )
233
+
234
+ # Extract transcript text from response
235
+ # When response_format="text", the response IS the text string
236
+ transcript_text = transcription
237
+
238
+ except Exception as e:
239
+ # Catch any Groq API errors (rate limits, network issues, etc.)
240
+ raise HTTPException(
241
+ status_code=500,
242
+ detail=f"Transcription failed: {str(e)}. Please try again."
243
+ )
244
+
245
+ # ========================================
246
+ # STEP 4: Validate transcript is not empty
247
+ # ========================================
248
+ if not transcript_text or len(transcript_text.strip()) == 0:
249
+ raise HTTPException(
250
+ status_code=400,
251
+ detail="No speech detected in audio file. Please ensure the recording contains clear speech."
252
+ )
253
+
254
+ # ========================================
255
+ # STEP 5: Return successful response
256
+ # ========================================
257
+ return TranscribeResponse(
258
+ transcript=transcript_text.strip(), # Remove leading/trailing whitespace
259
+ file_size_mb=round(size_mb, 2), # Round to 2 decimal places
260
+ filename=file.filename, # Original filename
261
+ success=True # Success flag
262
+ )
263
+
264
+ @app.post("/generate-minutes", response_model=GenerateMinutesResponse)
265
+ async def generate_minutes(request: GenerateMinutesRequest):
266
+ """
267
+ Generate formatted meeting minutes from raw transcript using Groq LLM
268
+
269
+ FLOW:
270
+ 1. Receive raw transcript text
271
+ 2. Validate transcript is not empty
272
+ 3. Build messages array (system prompt + user transcript)
273
+ 4. Call Groq LLM (gpt-oss-120b)
274
+ 5. Receive formatted Markdown minutes
275
+ 6. Validate output is not empty
276
+ 7. Return formatted minutes
277
+
278
+ Args:
279
+ request: GenerateMinutesRequest containing transcript text
280
+
281
+ Returns:
282
+ GenerateMinutesResponse: Contains formatted Markdown minutes
283
+
284
+ Raises:
285
+ HTTPException 400: Empty transcript
286
+ HTTPException 500: Groq API error
287
+ """
288
+
289
+ # ========================================
290
+ # STEP 1: Validate transcript is not empty
291
+ # ========================================
292
+ if not request.transcript or len(request.transcript.strip()) == 0:
293
+ raise HTTPException(
294
+ status_code=400,
295
+ detail="Transcript cannot be empty. Please provide a valid transcript."
296
+ )
297
+
298
+ # ========================================
299
+ # STEP 2: Build messages array for LLM
300
+ # ========================================
301
+ messages = [
302
+ {
303
+ "role": "system",
304
+ "content": MINUTES_SYSTEM_PROMPT
305
+ },
306
+ {
307
+ "role": "user",
308
+ "content": f"Please convert the following meeting transcript into structured minutes:\n\n{request.transcript}"
309
+ }
310
+ ]
311
+
312
+ # ========================================
313
+ # STEP 3: Call Groq LLM API
314
+ # ========================================
315
+ try:
316
+ # Create chat completion request
317
+ completion = groq_client.chat.completions.create(
318
+ model=LLM_MODEL, # openai/gpt-oss-120b
319
+ messages=messages, # System prompt + user transcript
320
+ temperature=LLM_TEMPERATURE, # 0.1 for deterministic output
321
+ max_completion_tokens=MAX_COMPLETION_TOKENS, # 1024 tokens max
322
+ top_p=1, # Standard sampling
323
+ stream=False, # Get complete response at once
324
+ stop=None # No custom stop sequences
325
+ )
326
+
327
+ # Extract generated minutes from response
328
+ minutes_text = completion.choices[0].message.content
329
+
330
+ except Exception as e:
331
+ # Catch any Groq API errors (rate limits, network issues, etc.)
332
+ raise HTTPException(
333
+ status_code=500,
334
+ detail=f"Minutes generation failed: {str(e)}. Please try again."
335
+ )
336
+
337
+ # ========================================
338
+ # STEP 4: Validate minutes are not empty
339
+ # ========================================
340
+ if not minutes_text or len(minutes_text.strip()) == 0:
341
+ raise HTTPException(
342
+ status_code=500,
343
+ detail="LLM returned empty response. Please try again."
344
+ )
345
+
346
+ # ========================================
347
+ # STEP 5: Return successful response
348
+ # ========================================
349
+ return GenerateMinutesResponse(
350
+ minutes=minutes_text.strip(), # Remove leading/trailing whitespace
351
+ success=True # Success flag
352
+ )
353
+
354
+ # ============================================
355
+ # RUN SERVER (for local testing)
356
+ # ============================================
357
+ if __name__ == "__main__":
358
+ import uvicorn
359
+
360
+ print("πŸš€ Starting Meeting Minutes Backend...")
361
+ print("πŸ“ Server will run on: http://localhost:8000")
362
+ print("πŸ“– API docs available at: http://localhost:8000/docs")
363
+ print("πŸ” Health check: http://localhost:8000")
364
+ print("\nβœ… Available endpoints:")
365
+ print(" POST /transcribe - Convert audio to text")
366
+ print(" POST /generate-minutes - Convert transcript to formatted minutes")
367
+
368
+ # Run the FastAPI app with uvicorn
369
+ uvicorn.run(
370
+ app,
371
+ host="0.0.0.0", # Listen on all network interfaces
372
+ port=8001, # Port 8000 (standard for APIs)
373
+ log_level="info" # Show request logs
374
  )