suryateja008 commited on
Commit
a3c7eb4
ยท
verified ยท
1 Parent(s): 7361bc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +682 -682
app.py CHANGED
@@ -1,682 +1,682 @@
1
- import os
2
- import requests
3
- import json
4
- from dotenv import load_dotenv
5
- from langchain_groq import ChatGroq
6
- from langchain.text_splitter import RecursiveCharacterTextSplitter
7
- from langchain_community.vectorstores import Chroma
8
- from langchain_community.embeddings import HuggingFaceEmbeddings
9
- from langchain.chains import RetrievalQA
10
- from langchain.prompts import PromptTemplate
11
- from fastapi import FastAPI, HTTPException
12
- from fastapi.middleware.cors import CORSMiddleware
13
- from pydantic import BaseModel
14
- from typing import Optional, List
15
- from tqdm import tqdm
16
- import pandas as pd
17
- import uvicorn
18
- from deep_translator import GoogleTranslator
19
- from gtts import gTTS
20
- import base64
21
- from io import BytesIO
22
-
23
- # Load environment variables
24
- load_dotenv()
25
-
26
- # Supported Indian Languages
27
- SUPPORTED_LANGUAGES = {
28
- 'en': 'English',
29
- 'hi': 'Hindi',
30
- 'te': 'Telugu',
31
- 'ta': 'Tamil',
32
- 'ml': 'Malayalam',
33
- 'kn': 'Kannada',
34
- 'bn': 'Bengali',
35
- 'mr': 'Marathi',
36
- 'gu': 'Gujarati',
37
- 'pa': 'Punjabi',
38
- 'ur': 'Urdu',
39
- 'or': 'Odia',
40
- 'as': 'Assamese'
41
- }
42
-
43
- class TranslationService:
44
- """Service for translating text between languages"""
45
-
46
- @staticmethod
47
- def translate_text(text: str, source_lang: str, target_lang: str) -> str:
48
- """
49
- Translate text from source language to target language
50
-
51
- Args:
52
- text: Text to translate
53
- source_lang: Source language code (e.g., 'hi', 'te')
54
- target_lang: Target language code (e.g., 'en')
55
-
56
- Returns:
57
- Translated text
58
- """
59
- if source_lang == target_lang:
60
- return text
61
-
62
- try:
63
- translator = GoogleTranslator(source=source_lang, target=target_lang)
64
- translated = translator.translate(text)
65
- return translated
66
- except Exception as e:
67
- print(f"Translation error ({source_lang} -> {target_lang}): {e}")
68
- return text # Return original text if translation fails
69
-
70
- @staticmethod
71
- def text_to_speech(text: str, lang_code: str) -> str:
72
- """
73
- Convert text to speech and return base64 encoded audio
74
-
75
- Args:
76
- text: Text to convert to speech
77
- lang_code: Language code for TTS
78
-
79
- Returns:
80
- Base64 encoded MP3 audio
81
- """
82
- try:
83
- # Create TTS
84
- tts = gTTS(text=text, lang=lang_code, slow=False)
85
-
86
- # Save to BytesIO buffer
87
- audio_buffer = BytesIO()
88
- tts.write_to_fp(audio_buffer)
89
- audio_buffer.seek(0)
90
-
91
- # Encode to base64
92
- audio_base64 = base64.b64encode(audio_buffer.read()).decode('utf-8')
93
- return audio_base64
94
-
95
- except Exception as e:
96
- print(f"TTS error for language {lang_code}: {e}")
97
- return ""
98
-
99
- class GovernmentSchemesRAG:
100
- def __init__(self):
101
- self.groq_api_key = os.getenv("GROQ_API_KEY")
102
- if not self.groq_api_key or self.groq_api_key == "your_groq_api_key_here":
103
- raise ValueError("Please set your GROQ_API_KEY in the .env file. Get it from https://console.groq.com/")
104
-
105
- # Initialize embeddings (free HuggingFace model)
106
- print("Loading embedding model...")
107
- self.embeddings = HuggingFaceEmbeddings(
108
- model_name="sentence-transformers/all-MiniLM-L6-v2",
109
- model_kwargs={'device': 'cpu'}
110
- )
111
-
112
- # Initialize LLM (free Groq API)
113
- self.llm = ChatGroq(
114
- temperature=0.3,
115
- model_name="llama-3.3-70b-versatile", # Latest free tier model
116
- groq_api_key=self.groq_api_key
117
- )
118
-
119
- self.vectorstore = None
120
- self.qa_chain = None
121
-
122
- def load_vectorstore(self):
123
- """
124
- Load existing vector database from disk
125
- """
126
- print("Loading vector database from ./chroma_db/...")
127
-
128
- if not os.path.exists("./chroma_db"):
129
- raise FileNotFoundError(
130
- "Vector database not found! Please run 'python setup_db.py' first to create the database."
131
- )
132
-
133
- self.vectorstore = Chroma(
134
- persist_directory="./chroma_db",
135
- embedding_function=self.embeddings
136
- )
137
-
138
- print("โœ… Vector database loaded successfully!")
139
- return self.vectorstore
140
-
141
- def setup_qa_chain(self):
142
- """
143
- Setup the QA chain with custom prompt
144
- """
145
- # Custom prompt template for government schemes
146
- prompt_template = """You are a helpful assistant that provides information about Indian government schemes.
147
- Use the following pieces of context to answer the question at the end.
148
- If you don't find the exact answer in the context, provide the most relevant schemes based on the available information.
149
-
150
- Context: {context}
151
-
152
- Question: {question}
153
-
154
- Instructions:
155
- 1. Identify the key requirements from the question (age group, education level, state, category, etc.)
156
- 2. List all relevant schemes that match the criteria
157
- 3. For each scheme, provide:
158
- - Scheme name
159
- - Eligibility criteria
160
- - Benefits
161
- - How to apply (if mentioned)
162
- 4. If the user mentions a state, prioritize schemes for that state, but also include national schemes
163
- 5. Be specific and helpful in your response
164
-
165
- Answer:"""
166
-
167
- PROMPT = PromptTemplate(
168
- template=prompt_template,
169
- input_variables=["context", "question"]
170
- )
171
-
172
- self.qa_chain = RetrievalQA.from_chain_type(
173
- llm=self.llm,
174
- chain_type="stuff",
175
- retriever=self.vectorstore.as_retriever(
176
- search_kwargs={"k": 5} # Retrieve top 5 relevant documents
177
- ),
178
- chain_type_kwargs={"prompt": PROMPT},
179
- return_source_documents=True
180
- )
181
-
182
- print("QA Chain setup complete!")
183
-
184
- def initialize(self):
185
- """
186
- Initialize the RAG system by loading existing vector database
187
- """
188
- # Load existing vectorstore from disk
189
- self.load_vectorstore()
190
-
191
- # Setup QA chain
192
- self.setup_qa_chain()
193
-
194
- print("\nโœ… RAG System initialized successfully!")
195
-
196
- def query(self, question, state=None):
197
- """
198
- Query the RAG system
199
- """
200
- if state and state != "All States":
201
- question = f"{question} (User is from {state})"
202
-
203
- result = self.qa_chain.invoke({"query": question})
204
-
205
- # Format response
206
- response = result['result']
207
-
208
- # Add source information
209
- source_info = "\n\n๐Ÿ“š Sources:\n"
210
- for i, doc in enumerate(result['source_documents'][:3], 1):
211
- source_info += f"{i}. {doc.page_content[:150]}...\n"
212
-
213
- return response + source_info
214
-
215
-
216
- # Initialize the RAG system
217
- print("๐Ÿš€ Initializing Government Schemes RAG System...")
218
-
219
- try:
220
- rag_system = GovernmentSchemesRAG()
221
- rag_system.initialize()
222
- except FileNotFoundError as e:
223
- print("\n" + "="*80)
224
- print("โŒ ERROR: Vector database not found!")
225
- print("="*80)
226
- print("\n๐Ÿ“‹ Please run the setup script first:")
227
- print(" python setup_db.py")
228
- print("\nThis will create the vector database from updated_data.csv")
229
- print("="*80)
230
- exit(1)
231
- except Exception as e:
232
- print(f"\nโŒ Error initializing RAG system: {e}")
233
- exit(1)
234
-
235
- # Initialize FastAPI app
236
- app = FastAPI(
237
- title="Government Schemes RAG API",
238
- description="API for querying Indian Government Schemes using RAG",
239
- version="1.0.0"
240
- )
241
-
242
- # Add CORS middleware
243
- app.add_middleware(
244
- CORSMiddleware,
245
- allow_origins=["*"], # In production, replace with specific origins
246
- allow_credentials=True,
247
- allow_methods=["*"],
248
- allow_headers=["*"],
249
- )
250
-
251
- # Request models
252
- class QueryRequest(BaseModel):
253
- question: str
254
- state: Optional[str] = None
255
- language: Optional[str] = "en" # User's selected language (default: English)
256
-
257
- class QueryResponse(BaseModel):
258
- answer: str
259
- sources: List[str]
260
-
261
- class AudioRequest(BaseModel):
262
- text: str
263
- language: Optional[str] = "en"
264
-
265
- class AudioResponse(BaseModel):
266
- audio: str # Base64 encoded audio
267
-
268
- # Indian states list
269
- INDIAN_STATES = [
270
- "All States",
271
- "Andhra Pradesh", "Arunachal Pradesh", "Assam", "Bihar", "Chhattisgarh",
272
- "Goa", "Gujarat", "Haryana", "Himachal Pradesh", "Jharkhand", "Karnataka",
273
- "Kerala", "Madhya Pradesh", "Maharashtra", "Manipur", "Meghalaya", "Mizoram",
274
- "Nagaland", "Odisha", "Punjab", "Rajasthan", "Sikkim", "Tamil Nadu",
275
- "Telangana", "Tripura", "Uttar Pradesh", "Uttarakhand", "West Bengal",
276
- "Andaman and Nicobar Islands", "Chandigarh", "Dadra and Nagar Haveli and Daman and Diu",
277
- "Delhi", "Jammu and Kashmir", "Ladakh", "Lakshadweep", "Puducherry"
278
- ]
279
-
280
- # API Endpoints
281
- @app.get("/")
282
- async def root():
283
- """Root endpoint - API information"""
284
- return {
285
- "message": "Government Schemes RAG API with Multilingual Support",
286
- "version": "2.0.0",
287
- "supported_languages": SUPPORTED_LANGUAGES,
288
- "endpoints": {
289
- "POST /query": "Query government schemes with translation support",
290
- "POST /generate-audio": "Generate audio from text (on-demand)",
291
- "GET /states": "Get list of Indian states",
292
- "GET /languages": "Get list of supported languages",
293
- "GET /health": "Health check"
294
- }
295
- }
296
-
297
- @app.get("/health")
298
- async def health_check():
299
- """Health check endpoint"""
300
- return {
301
- "status": "healthy",
302
- "rag_system": "initialized" if rag_system.qa_chain is not None else "not initialized"
303
- }
304
-
305
- @app.get("/languages")
306
- async def get_languages():
307
- """Get list of supported languages"""
308
- return {
309
- "languages": SUPPORTED_LANGUAGES
310
- }
311
-
312
- @app.get("/states")
313
- async def get_states():
314
- """Get list of Indian states"""
315
- return {
316
- "states": INDIAN_STATES
317
- }
318
-
319
- @app.post("/query", response_model=QueryResponse)
320
- async def query_schemes(request: QueryRequest):
321
- """
322
- Query government schemes with multilingual support
323
-
324
- - **question**: The question about government schemes (in any supported language)
325
- - **state**: Optional state filter (default: None)
326
- - **language**: Language code for input/output (default: 'en')
327
-
328
- Flow:
329
- 1. Translate user question from selected language to English
330
- 2. Query RAG system (in English)
331
- 3. Translate answer back to user's selected language
332
- 4. Optionally generate audio response
333
- """
334
- if not request.question.strip():
335
- raise HTTPException(status_code=400, detail="Question cannot be empty")
336
-
337
- # Validate language code
338
- if request.language not in SUPPORTED_LANGUAGES:
339
- raise HTTPException(
340
- status_code=400,
341
- detail=f"Unsupported language. Supported: {list(SUPPORTED_LANGUAGES.keys())}"
342
- )
343
-
344
- try:
345
- # Step 1: Translate input question to English (if not already in English)
346
- if request.language != 'en':
347
- print(f"Translating question from {SUPPORTED_LANGUAGES[request.language]} to English...")
348
- english_question = TranslationService.translate_text(
349
- request.question,
350
- source_lang=request.language,
351
- target_lang='en'
352
- )
353
- print(f"Original: {request.question}")
354
- print(f"English: {english_question}")
355
- else:
356
- english_question = request.question
357
-
358
- # Step 2: Query the RAG system (in English)
359
- print(f"Querying RAG system with: {english_question}")
360
- result = rag_system.qa_chain.invoke({"query": english_question})
361
-
362
- # Extract English answer
363
- answer_english = result['result']
364
-
365
- # Step 3: Translate answer back to user's language (if not English)
366
- if request.language != 'en':
367
- print(f"Translating answer to {SUPPORTED_LANGUAGES[request.language]}...")
368
- final_answer = TranslationService.translate_text(
369
- answer_english,
370
- source_lang='en',
371
- target_lang=request.language
372
- )
373
- else:
374
- final_answer = answer_english
375
-
376
- # Step 4: Extract sources
377
- sources = []
378
- for doc in result['source_documents'][:3]:
379
- sources.append(doc.page_content[:200] + "...")
380
-
381
- # Note: Audio is NOT generated automatically
382
- # User must call /generate-audio endpoint when they click the speaker button
383
-
384
- return QueryResponse(
385
- answer=final_answer,
386
- sources=sources
387
- )
388
-
389
- except Exception as e:
390
- print(f"Error processing query: {str(e)}")
391
- raise HTTPException(status_code=500, detail=f"Error processing query: {str(e)}")
392
-
393
- @app.post("/generate-audio", response_model=AudioResponse)
394
- async def generate_audio(request: AudioRequest):
395
- """
396
- Generate audio from text (called when user clicks speaker button)
397
-
398
- - **text**: The text to convert to speech
399
- - **language**: Language code for TTS (default: 'en')
400
-
401
- This endpoint should be called ONLY when user explicitly clicks
402
- the "Play Audio" or speaker button on the UI.
403
- """
404
- if not request.text.strip():
405
- raise HTTPException(status_code=400, detail="Text cannot be empty")
406
-
407
- # Validate language code
408
- if request.language not in SUPPORTED_LANGUAGES:
409
- raise HTTPException(
410
- status_code=400,
411
- detail=f"Unsupported language. Supported: {list(SUPPORTED_LANGUAGES.keys())}"
412
- )
413
-
414
- try:
415
- print(f"Generating audio for language: {SUPPORTED_LANGUAGES[request.language]}")
416
-
417
- # Generate audio
418
- audio_base64 = TranslationService.text_to_speech(request.text, request.language)
419
-
420
- if not audio_base64:
421
- raise HTTPException(status_code=500, detail="Failed to generate audio")
422
-
423
- return AudioResponse(audio=audio_base64)
424
-
425
- except Exception as e:
426
- print(f"Error generating audio: {str(e)}")
427
- raise HTTPException(status_code=500, detail=f"Error generating audio: {str(e)}")
428
-
429
- # Request/Response models for schemes
430
- class SchemeFilterRequest(BaseModel):
431
- state: Optional[str] = None
432
- category: Optional[str] = None
433
- level: Optional[str] = None # Central, State
434
- search_text: Optional[str] = None
435
- page: Optional[int] = 1
436
- page_size: Optional[int] = 10
437
-
438
- class SchemeDetail(BaseModel):
439
- scheme_name: str
440
- slug: str
441
- details: str
442
- benefits: str
443
- eligibility: str
444
- application: str
445
- documents: str
446
- level: str
447
- scheme_category: str
448
- tags: str
449
-
450
- class SchemeListResponse(BaseModel):
451
- total: int
452
- page: int
453
- page_size: int
454
- total_pages: int
455
- schemes: List[SchemeDetail]
456
-
457
- @app.get("/schemes/all")
458
- async def get_all_schemes(
459
- page: int = 1,
460
- page_size: int = 10,
461
- state: Optional[str] = None,
462
- category: Optional[str] = None,
463
- level: Optional[str] = None,
464
- search: Optional[str] = None
465
- ):
466
- """
467
- Get all schemes with optional filtering and pagination
468
-
469
- - **page**: Page number (default: 1)
470
- - **page_size**: Number of schemes per page (default: 10, max: 100)
471
- - **state**: Filter by state (optional)
472
- - **category**: Filter by category (optional)
473
- - **level**: Filter by level - Central/State (optional)
474
- - **search**: Search in scheme name, details, benefits (optional)
475
-
476
- Returns paginated list of schemes with filtering options.
477
-
478
- **Recommendation**: Use BACKEND filtering for better performance and consistency.
479
- """
480
- try:
481
- # Load the CSV file
482
- df = pd.read_csv('updated_data.csv')
483
-
484
- # Apply filters
485
- filtered_df = df.copy()
486
-
487
- # Filter by state (case-insensitive partial match)
488
- if state and state != "All States":
489
- filtered_df = filtered_df[
490
- filtered_df['details'].str.contains(state, case=False, na=False) |
491
- filtered_df['eligibility'].str.contains(state, case=False, na=False)
492
- ]
493
-
494
- # Filter by category
495
- if category:
496
- filtered_df = filtered_df[
497
- filtered_df['schemeCategory'].str.contains(category, case=False, na=False)
498
- ]
499
-
500
- # Filter by level
501
- if level:
502
- filtered_df = filtered_df[
503
- filtered_df['level'].str.lower() == level.lower()
504
- ]
505
-
506
- # Search across multiple fields
507
- if search:
508
- search_mask = (
509
- filtered_df['scheme_name'].str.contains(search, case=False, na=False) |
510
- filtered_df['details'].str.contains(search, case=False, na=False) |
511
- filtered_df['benefits'].str.contains(search, case=False, na=False) |
512
- filtered_df['tags'].str.contains(search, case=False, na=False)
513
- )
514
- filtered_df = filtered_df[search_mask]
515
-
516
- # Calculate pagination
517
- total_schemes = len(filtered_df)
518
- page_size = min(page_size, 100) # Max 100 per page
519
- total_pages = (total_schemes + page_size - 1) // page_size
520
-
521
- # Get paginated data
522
- start_idx = (page - 1) * page_size
523
- end_idx = start_idx + page_size
524
- paginated_df = filtered_df.iloc[start_idx:end_idx]
525
-
526
- # Convert to list of dicts
527
- schemes = []
528
- for _, row in paginated_df.iterrows():
529
- schemes.append({
530
- "scheme_name": str(row.get('scheme_name', '')),
531
- "slug": str(row.get('slug', '')),
532
- "details": str(row.get('details', '')),
533
- "benefits": str(row.get('benefits', '')),
534
- "eligibility": str(row.get('eligibility', '')),
535
- "application": str(row.get('application', '')),
536
- "documents": str(row.get('documents', '')),
537
- "level": str(row.get('level', '')),
538
- "scheme_category": str(row.get('schemeCategory', '')),
539
- "tags": str(row.get('tags', ''))
540
- })
541
-
542
- return {
543
- "total": total_schemes,
544
- "page": page,
545
- "page_size": page_size,
546
- "total_pages": total_pages,
547
- "schemes": schemes
548
- }
549
-
550
- except Exception as e:
551
- print(f"Error fetching schemes: {str(e)}")
552
- raise HTTPException(status_code=500, detail=f"Error fetching schemes: {str(e)}")
553
-
554
- @app.get("/schemes/{slug}")
555
- async def get_scheme_by_slug(slug: str, language: Optional[str] = "en"):
556
- """
557
- Get detailed information about a specific scheme by slug
558
-
559
- - **slug**: The unique slug identifier of the scheme
560
- - **language**: Language code for translation (default: 'en')
561
-
562
- Returns detailed scheme information in the requested language.
563
- """
564
- try:
565
- # Load the CSV file
566
- df = pd.read_csv('updated_data.csv')
567
-
568
- # Find scheme by slug
569
- scheme_row = df[df['slug'] == slug]
570
-
571
- if scheme_row.empty:
572
- raise HTTPException(status_code=404, detail="Scheme not found")
573
-
574
- scheme_row = scheme_row.iloc[0]
575
-
576
- # Prepare scheme details
577
- scheme_data = {
578
- "scheme_name": str(scheme_row.get('scheme_name', '')),
579
- "slug": str(scheme_row.get('slug', '')),
580
- "details": str(scheme_row.get('details', '')),
581
- "benefits": str(scheme_row.get('benefits', '')),
582
- "eligibility": str(scheme_row.get('eligibility', '')),
583
- "application": str(scheme_row.get('application', '')),
584
- "documents": str(scheme_row.get('documents', '')),
585
- "level": str(scheme_row.get('level', '')),
586
- "scheme_category": str(scheme_row.get('schemeCategory', '')),
587
- "tags": str(scheme_row.get('tags', ''))
588
- }
589
-
590
- # Translate if needed
591
- if language != 'en' and language in SUPPORTED_LANGUAGES:
592
- print(f"Translating scheme details to {SUPPORTED_LANGUAGES[language]}...")
593
- scheme_data['scheme_name'] = TranslationService.translate_text(
594
- scheme_data['scheme_name'], 'en', language
595
- )
596
- scheme_data['details'] = TranslationService.translate_text(
597
- scheme_data['details'], 'en', language
598
- )
599
- scheme_data['benefits'] = TranslationService.translate_text(
600
- scheme_data['benefits'], 'en', language
601
- )
602
- scheme_data['eligibility'] = TranslationService.translate_text(
603
- scheme_data['eligibility'], 'en', language
604
- )
605
-
606
- return scheme_data
607
-
608
- except HTTPException:
609
- raise
610
- except Exception as e:
611
- print(f"Error fetching scheme: {str(e)}")
612
- raise HTTPException(status_code=500, detail=f"Error fetching scheme: {str(e)}")
613
-
614
- @app.get("/schemes/categories")
615
- async def get_scheme_categories():
616
- """
617
- Get all unique scheme categories available
618
-
619
- Returns list of all unique categories from the database.
620
- """
621
- try:
622
- df = pd.read_csv('updated_data.csv')
623
-
624
- # Get unique categories (may contain comma-separated values)
625
- all_categories = set()
626
- for cat in df['schemeCategory'].dropna():
627
- # Split by comma and strip whitespace
628
- categories = [c.strip() for c in str(cat).split(',')]
629
- all_categories.update(categories)
630
-
631
- return {
632
- "categories": sorted(list(all_categories))
633
- }
634
-
635
- except Exception as e:
636
- print(f"Error fetching categories: {str(e)}")
637
- raise HTTPException(status_code=500, detail=f"Error fetching categories: {str(e)}")
638
-
639
- @app.get("/schemes/stats")
640
- async def get_scheme_statistics():
641
- """
642
- Get statistics about schemes in the database
643
-
644
- Returns:
645
- - Total number of schemes
646
- - Count by level (Central/State)
647
- - Count by categories
648
- - Count by states
649
- """
650
- try:
651
- df = pd.read_csv('updated_data.csv')
652
-
653
- # Total schemes
654
- total = len(df)
655
-
656
- # Count by level
657
- level_counts = df['level'].value_counts().to_dict()
658
-
659
- # Count by category
660
- category_counts = {}
661
- for cat in df['schemeCategory'].dropna():
662
- categories = [c.strip() for c in str(cat).split(',')]
663
- for c in categories:
664
- category_counts[c] = category_counts.get(c, 0) + 1
665
-
666
- return {
667
- "total_schemes": total,
668
- "by_level": level_counts,
669
- "by_category": dict(sorted(category_counts.items(), key=lambda x: x[1], reverse=True)[:10]),
670
- "total_categories": len(category_counts)
671
- }
672
-
673
- except Exception as e:
674
- print(f"Error fetching statistics: {str(e)}")
675
- raise HTTPException(status_code=500, detail=f"Error fetching statistics: {str(e)}")
676
-
677
- # Launch the app
678
- if __name__ == "__main__":
679
- print("\n๐ŸŒ Starting FastAPI server...")
680
- print("๐Ÿ“– API Documentation: http://127.0.0.1:8000/docs")
681
- print("๐Ÿ“Š Alternative docs: http://127.0.0.1:8000/redoc")
682
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ import os
2
+ import requests
3
+ import json
4
+ from dotenv import load_dotenv
5
+ from langchain_groq import ChatGroq
6
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
7
+ from langchain_community.vectorstores import Chroma
8
+ from langchain_community.embeddings import HuggingFaceEmbeddings
9
+ from langchain.chains import RetrievalQA
10
+ from langchain.prompts import PromptTemplate
11
+ from fastapi import FastAPI, HTTPException
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from pydantic import BaseModel
14
+ from typing import Optional, List
15
+ from tqdm import tqdm
16
+ import pandas as pd
17
+ import uvicorn
18
+ from deep_translator import GoogleTranslator
19
+ from gtts import gTTS
20
+ import base64
21
+ from io import BytesIO
22
+
23
+ # Load environment variables
24
+ load_dotenv()
25
+
26
+ # Supported Indian Languages
27
+ SUPPORTED_LANGUAGES = {
28
+ 'en': 'English',
29
+ 'hi': 'Hindi',
30
+ 'te': 'Telugu',
31
+ 'ta': 'Tamil',
32
+ 'ml': 'Malayalam',
33
+ 'kn': 'Kannada',
34
+ 'bn': 'Bengali',
35
+ 'mr': 'Marathi',
36
+ 'gu': 'Gujarati',
37
+ 'pa': 'Punjabi',
38
+ 'ur': 'Urdu',
39
+ 'or': 'Odia',
40
+ 'as': 'Assamese'
41
+ }
42
+
43
+ class TranslationService:
44
+ """Service for translating text between languages"""
45
+
46
+ @staticmethod
47
+ def translate_text(text: str, source_lang: str, target_lang: str) -> str:
48
+ """
49
+ Translate text from source language to target language
50
+
51
+ Args:
52
+ text: Text to translate
53
+ source_lang: Source language code (e.g., 'hi', 'te')
54
+ target_lang: Target language code (e.g., 'en')
55
+
56
+ Returns:
57
+ Translated text
58
+ """
59
+ if source_lang == target_lang:
60
+ return text
61
+
62
+ try:
63
+ translator = GoogleTranslator(source=source_lang, target=target_lang)
64
+ translated = translator.translate(text)
65
+ return translated
66
+ except Exception as e:
67
+ print(f"Translation error ({source_lang} -> {target_lang}): {e}")
68
+ return text # Return original text if translation fails
69
+
70
+ @staticmethod
71
+ def text_to_speech(text: str, lang_code: str) -> str:
72
+ """
73
+ Convert text to speech and return base64 encoded audio
74
+
75
+ Args:
76
+ text: Text to convert to speech
77
+ lang_code: Language code for TTS
78
+
79
+ Returns:
80
+ Base64 encoded MP3 audio
81
+ """
82
+ try:
83
+ # Create TTS
84
+ tts = gTTS(text=text, lang=lang_code, slow=False)
85
+
86
+ # Save to BytesIO buffer
87
+ audio_buffer = BytesIO()
88
+ tts.write_to_fp(audio_buffer)
89
+ audio_buffer.seek(0)
90
+
91
+ # Encode to base64
92
+ audio_base64 = base64.b64encode(audio_buffer.read()).decode('utf-8')
93
+ return audio_base64
94
+
95
+ except Exception as e:
96
+ print(f"TTS error for language {lang_code}: {e}")
97
+ return ""
98
+
99
+ class GovernmentSchemesRAG:
100
+ def __init__(self):
101
+ self.groq_api_key = os.getenv("GROQ_API_KEY")
102
+ if not self.groq_api_key or self.groq_api_key == "your_groq_api_key_here":
103
+ raise ValueError("Please set your GROQ_API_KEY in the .env file. Get it from https://console.groq.com/")
104
+
105
+ # Initialize embeddings (free HuggingFace model)
106
+ print("Loading embedding model...")
107
+ self.embeddings = HuggingFaceEmbeddings(
108
+ model_name="sentence-transformers/all-MiniLM-L6-v2",
109
+ model_kwargs={'device': 'cpu'}
110
+ )
111
+
112
+ # Initialize LLM (free Groq API)
113
+ self.llm = ChatGroq(
114
+ temperature=0.3,
115
+ model_name="llama-3.3-70b-versatile", # Latest free tier model
116
+ groq_api_key=self.groq_api_key
117
+ )
118
+
119
+ self.vectorstore = None
120
+ self.qa_chain = None
121
+
122
+ def load_vectorstore(self):
123
+ """
124
+ Load existing vector database from disk
125
+ """
126
+ print("Loading vector database from ./chroma_db/...")
127
+
128
+ if not os.path.exists("./chroma_db"):
129
+ raise FileNotFoundError(
130
+ "Vector database not found! Please run 'python setup_db.py' first to create the database."
131
+ )
132
+
133
+ self.vectorstore = Chroma(
134
+ persist_directory="./chroma_db",
135
+ embedding_function=self.embeddings
136
+ )
137
+
138
+ print("โœ… Vector database loaded successfully!")
139
+ return self.vectorstore
140
+
141
+ def setup_qa_chain(self):
142
+ """
143
+ Setup the QA chain with custom prompt
144
+ """
145
+ # Custom prompt template for government schemes
146
+ prompt_template = """You are a helpful assistant that provides information about Indian government schemes.
147
+ Use the following pieces of context to answer the question at the end.
148
+ If you don't find the exact answer in the context, provide the most relevant schemes based on the available information.
149
+
150
+ Context: {context}
151
+
152
+ Question: {question}
153
+
154
+ Instructions:
155
+ 1. Identify the key requirements from the question (age group, education level, state, category, etc.)
156
+ 2. List all relevant schemes that match the criteria
157
+ 3. For each scheme, provide:
158
+ - Scheme name
159
+ - Eligibility criteria
160
+ - Benefits
161
+ - How to apply (if mentioned)
162
+ 4. If the user mentions a state, prioritize schemes for that state, but also include national schemes
163
+ 5. Be specific and helpful in your response
164
+
165
+ Answer:"""
166
+
167
+ PROMPT = PromptTemplate(
168
+ template=prompt_template,
169
+ input_variables=["context", "question"]
170
+ )
171
+
172
+ self.qa_chain = RetrievalQA.from_chain_type(
173
+ llm=self.llm,
174
+ chain_type="stuff",
175
+ retriever=self.vectorstore.as_retriever(
176
+ search_kwargs={"k": 5} # Retrieve top 5 relevant documents
177
+ ),
178
+ chain_type_kwargs={"prompt": PROMPT},
179
+ return_source_documents=True
180
+ )
181
+
182
+ print("QA Chain setup complete!")
183
+
184
+ def initialize(self):
185
+ """
186
+ Initialize the RAG system by loading existing vector database
187
+ """
188
+ # Load existing vectorstore from disk
189
+ self.load_vectorstore()
190
+
191
+ # Setup QA chain
192
+ self.setup_qa_chain()
193
+
194
+ print("\nโœ… RAG System initialized successfully!")
195
+
196
+ def query(self, question, state=None):
197
+ """
198
+ Query the RAG system
199
+ """
200
+ if state and state != "All States":
201
+ question = f"{question} (User is from {state})"
202
+
203
+ result = self.qa_chain.invoke({"query": question})
204
+
205
+ # Format response
206
+ response = result['result']
207
+
208
+ # Add source information
209
+ source_info = "\n\n๐Ÿ“š Sources:\n"
210
+ for i, doc in enumerate(result['source_documents'][:3], 1):
211
+ source_info += f"{i}. {doc.page_content[:150]}...\n"
212
+
213
+ return response + source_info
214
+
215
+
216
+ # Initialize the RAG system
217
+ print("๐Ÿš€ Initializing Government Schemes RAG System...")
218
+
219
+ try:
220
+ rag_system = GovernmentSchemesRAG()
221
+ rag_system.initialize()
222
+ except FileNotFoundError as e:
223
+ print("\n" + "="*80)
224
+ print("โŒ ERROR: Vector database not found!")
225
+ print("="*80)
226
+ print("\n๐Ÿ“‹ Please run the setup script first:")
227
+ print(" python setup_db.py")
228
+ print("\nThis will create the vector database from updated_data.csv")
229
+ print("="*80)
230
+ exit(1)
231
+ except Exception as e:
232
+ print(f"\nโŒ Error initializing RAG system: {e}")
233
+ exit(1)
234
+
235
+ # Initialize FastAPI app
236
+ app = FastAPI(
237
+ title="Government Schemes RAG API",
238
+ description="API for querying Indian Government Schemes using RAG",
239
+ version="1.0.0"
240
+ )
241
+
242
+ # Add CORS middleware
243
+ app.add_middleware(
244
+ CORSMiddleware,
245
+ allow_origins=["*"], # In production, replace with specific origins
246
+ allow_credentials=True,
247
+ allow_methods=["*"],
248
+ allow_headers=["*"],
249
+ )
250
+
251
+ # Request models
252
+ class QueryRequest(BaseModel):
253
+ question: str
254
+ state: Optional[str] = None
255
+ language: Optional[str] = "en" # User's selected language (default: English)
256
+
257
+ class QueryResponse(BaseModel):
258
+ answer: str
259
+ sources: List[str]
260
+
261
+ class AudioRequest(BaseModel):
262
+ text: str
263
+ language: Optional[str] = "en"
264
+
265
+ class AudioResponse(BaseModel):
266
+ audio: str # Base64 encoded audio
267
+
268
+ # Indian states list
269
+ INDIAN_STATES = [
270
+ "All States",
271
+ "Andhra Pradesh", "Arunachal Pradesh", "Assam", "Bihar", "Chhattisgarh",
272
+ "Goa", "Gujarat", "Haryana", "Himachal Pradesh", "Jharkhand", "Karnataka",
273
+ "Kerala", "Madhya Pradesh", "Maharashtra", "Manipur", "Meghalaya", "Mizoram",
274
+ "Nagaland", "Odisha", "Punjab", "Rajasthan", "Sikkim", "Tamil Nadu",
275
+ "Telangana", "Tripura", "Uttar Pradesh", "Uttarakhand", "West Bengal",
276
+ "Andaman and Nicobar Islands", "Chandigarh", "Dadra and Nagar Haveli and Daman and Diu",
277
+ "Delhi", "Jammu and Kashmir", "Ladakh", "Lakshadweep", "Puducherry"
278
+ ]
279
+
280
+ # API Endpoints
281
+ @app.get("/")
282
+ async def root():
283
+ """Root endpoint - API information"""
284
+ return {
285
+ "message": "Government Schemes RAG API with Multilingual Support",
286
+ "version": "2.0.0",
287
+ "supported_languages": SUPPORTED_LANGUAGES,
288
+ "endpoints": {
289
+ "POST /query": "Query government schemes with translation support",
290
+ "POST /generate-audio": "Generate audio from text (on-demand)",
291
+ "GET /states": "Get list of Indian states",
292
+ "GET /languages": "Get list of supported languages",
293
+ "GET /health": "Health check"
294
+ }
295
+ }
296
+
297
+ @app.get("/health")
298
+ async def health_check():
299
+ """Health check endpoint"""
300
+ return {
301
+ "status": "healthy",
302
+ "rag_system": "initialized" if rag_system.qa_chain is not None else "not initialized"
303
+ }
304
+
305
+ @app.get("/languages")
306
+ async def get_languages():
307
+ """Get list of supported languages"""
308
+ return {
309
+ "languages": SUPPORTED_LANGUAGES
310
+ }
311
+
312
+ @app.get("/states")
313
+ async def get_states():
314
+ """Get list of Indian states"""
315
+ return {
316
+ "states": INDIAN_STATES
317
+ }
318
+
319
+ @app.post("/query", response_model=QueryResponse)
320
+ async def query_schemes(request: QueryRequest):
321
+ """
322
+ Query government schemes with multilingual support
323
+
324
+ - **question**: The question about government schemes (in any supported language)
325
+ - **state**: Optional state filter (default: None)
326
+ - **language**: Language code for input/output (default: 'en')
327
+
328
+ Flow:
329
+ 1. Translate user question from selected language to English
330
+ 2. Query RAG system (in English)
331
+ 3. Translate answer back to user's selected language
332
+ 4. Optionally generate audio response
333
+ """
334
+ if not request.question.strip():
335
+ raise HTTPException(status_code=400, detail="Question cannot be empty")
336
+
337
+ # Validate language code
338
+ if request.language not in SUPPORTED_LANGUAGES:
339
+ raise HTTPException(
340
+ status_code=400,
341
+ detail=f"Unsupported language. Supported: {list(SUPPORTED_LANGUAGES.keys())}"
342
+ )
343
+
344
+ try:
345
+ # Step 1: Translate input question to English (if not already in English)
346
+ if request.language != 'en':
347
+ print(f"Translating question from {SUPPORTED_LANGUAGES[request.language]} to English...")
348
+ english_question = TranslationService.translate_text(
349
+ request.question,
350
+ source_lang=request.language,
351
+ target_lang='en'
352
+ )
353
+ print(f"Original: {request.question}")
354
+ print(f"English: {english_question}")
355
+ else:
356
+ english_question = request.question
357
+
358
+ # Step 2: Query the RAG system (in English)
359
+ print(f"Querying RAG system with: {english_question}")
360
+ result = rag_system.qa_chain.invoke({"query": english_question})
361
+
362
+ # Extract English answer
363
+ answer_english = result['result']
364
+
365
+ # Step 3: Translate answer back to user's language (if not English)
366
+ if request.language != 'en':
367
+ print(f"Translating answer to {SUPPORTED_LANGUAGES[request.language]}...")
368
+ final_answer = TranslationService.translate_text(
369
+ answer_english,
370
+ source_lang='en',
371
+ target_lang=request.language
372
+ )
373
+ else:
374
+ final_answer = answer_english
375
+
376
+ # Step 4: Extract sources
377
+ sources = []
378
+ for doc in result['source_documents'][:3]:
379
+ sources.append(doc.page_content[:200] + "...")
380
+
381
+ # Note: Audio is NOT generated automatically
382
+ # User must call /generate-audio endpoint when they click the speaker button
383
+
384
+ return QueryResponse(
385
+ answer=final_answer,
386
+ sources=sources
387
+ )
388
+
389
+ except Exception as e:
390
+ print(f"Error processing query: {str(e)}")
391
+ raise HTTPException(status_code=500, detail=f"Error processing query: {str(e)}")
392
+
393
+ @app.post("/generate-audio", response_model=AudioResponse)
394
+ async def generate_audio(request: AudioRequest):
395
+ """
396
+ Generate audio from text (called when user clicks speaker button)
397
+
398
+ - **text**: The text to convert to speech
399
+ - **language**: Language code for TTS (default: 'en')
400
+
401
+ This endpoint should be called ONLY when user explicitly clicks
402
+ the "Play Audio" or speaker button on the UI.
403
+ """
404
+ if not request.text.strip():
405
+ raise HTTPException(status_code=400, detail="Text cannot be empty")
406
+
407
+ # Validate language code
408
+ if request.language not in SUPPORTED_LANGUAGES:
409
+ raise HTTPException(
410
+ status_code=400,
411
+ detail=f"Unsupported language. Supported: {list(SUPPORTED_LANGUAGES.keys())}"
412
+ )
413
+
414
+ try:
415
+ print(f"Generating audio for language: {SUPPORTED_LANGUAGES[request.language]}")
416
+
417
+ # Generate audio
418
+ audio_base64 = TranslationService.text_to_speech(request.text, request.language)
419
+
420
+ if not audio_base64:
421
+ raise HTTPException(status_code=500, detail="Failed to generate audio")
422
+
423
+ return AudioResponse(audio=audio_base64)
424
+
425
+ except Exception as e:
426
+ print(f"Error generating audio: {str(e)}")
427
+ raise HTTPException(status_code=500, detail=f"Error generating audio: {str(e)}")
428
+
429
+ # Request/Response models for schemes
430
+ class SchemeFilterRequest(BaseModel):
431
+ state: Optional[str] = None
432
+ category: Optional[str] = None
433
+ level: Optional[str] = None # Central, State
434
+ search_text: Optional[str] = None
435
+ page: Optional[int] = 1
436
+ page_size: Optional[int] = 10
437
+
438
+ class SchemeDetail(BaseModel):
439
+ scheme_name: str
440
+ slug: str
441
+ details: str
442
+ benefits: str
443
+ eligibility: str
444
+ application: str
445
+ documents: str
446
+ level: str
447
+ scheme_category: str
448
+ tags: str
449
+
450
+ class SchemeListResponse(BaseModel):
451
+ total: int
452
+ page: int
453
+ page_size: int
454
+ total_pages: int
455
+ schemes: List[SchemeDetail]
456
+
457
+ @app.get("/schemes/all")
458
+ async def get_all_schemes(
459
+ page: int = 1,
460
+ page_size: int = 10,
461
+ state: Optional[str] = None,
462
+ category: Optional[str] = None,
463
+ level: Optional[str] = None,
464
+ search: Optional[str] = None
465
+ ):
466
+ """
467
+ Get all schemes with optional filtering and pagination
468
+
469
+ - **page**: Page number (default: 1)
470
+ - **page_size**: Number of schemes per page (default: 10, max: 100)
471
+ - **state**: Filter by state (optional)
472
+ - **category**: Filter by category (optional)
473
+ - **level**: Filter by level - Central/State (optional)
474
+ - **search**: Search in scheme name, details, benefits (optional)
475
+
476
+ Returns paginated list of schemes with filtering options.
477
+
478
+ **Recommendation**: Use BACKEND filtering for better performance and consistency.
479
+ """
480
+ try:
481
+ # Load the CSV file
482
+ df = pd.read_csv('updated_data.csv')
483
+
484
+ # Apply filters
485
+ filtered_df = df.copy()
486
+
487
+ # Filter by state (case-insensitive partial match)
488
+ if state and state != "All States":
489
+ filtered_df = filtered_df[
490
+ filtered_df['details'].str.contains(state, case=False, na=False) |
491
+ filtered_df['eligibility'].str.contains(state, case=False, na=False)
492
+ ]
493
+
494
+ # Filter by category
495
+ if category:
496
+ filtered_df = filtered_df[
497
+ filtered_df['schemeCategory'].str.contains(category, case=False, na=False)
498
+ ]
499
+
500
+ # Filter by level
501
+ if level:
502
+ filtered_df = filtered_df[
503
+ filtered_df['level'].str.lower() == level.lower()
504
+ ]
505
+
506
+ # Search across multiple fields
507
+ if search:
508
+ search_mask = (
509
+ filtered_df['scheme_name'].str.contains(search, case=False, na=False) |
510
+ filtered_df['details'].str.contains(search, case=False, na=False) |
511
+ filtered_df['benefits'].str.contains(search, case=False, na=False) |
512
+ filtered_df['tags'].str.contains(search, case=False, na=False)
513
+ )
514
+ filtered_df = filtered_df[search_mask]
515
+
516
+ # Calculate pagination
517
+ total_schemes = len(filtered_df)
518
+ page_size = min(page_size, 100) # Max 100 per page
519
+ total_pages = (total_schemes + page_size - 1) // page_size
520
+
521
+ # Get paginated data
522
+ start_idx = (page - 1) * page_size
523
+ end_idx = start_idx + page_size
524
+ paginated_df = filtered_df.iloc[start_idx:end_idx]
525
+
526
+ # Convert to list of dicts
527
+ schemes = []
528
+ for _, row in paginated_df.iterrows():
529
+ schemes.append({
530
+ "scheme_name": str(row.get('scheme_name', '')),
531
+ "slug": str(row.get('slug', '')),
532
+ "details": str(row.get('details', '')),
533
+ "benefits": str(row.get('benefits', '')),
534
+ "eligibility": str(row.get('eligibility', '')),
535
+ "application": str(row.get('application', '')),
536
+ "documents": str(row.get('documents', '')),
537
+ "level": str(row.get('level', '')),
538
+ "scheme_category": str(row.get('schemeCategory', '')),
539
+ "tags": str(row.get('tags', ''))
540
+ })
541
+
542
+ return {
543
+ "total": total_schemes,
544
+ "page": page,
545
+ "page_size": page_size,
546
+ "total_pages": total_pages,
547
+ "schemes": schemes
548
+ }
549
+
550
+ except Exception as e:
551
+ print(f"Error fetching schemes: {str(e)}")
552
+ raise HTTPException(status_code=500, detail=f"Error fetching schemes: {str(e)}")
553
+
554
+ @app.get("/schemes/{slug}")
555
+ async def get_scheme_by_slug(slug: str, language: Optional[str] = "en"):
556
+ """
557
+ Get detailed information about a specific scheme by slug
558
+
559
+ - **slug**: The unique slug identifier of the scheme
560
+ - **language**: Language code for translation (default: 'en')
561
+
562
+ Returns detailed scheme information in the requested language.
563
+ """
564
+ try:
565
+ # Load the CSV file
566
+ df = pd.read_csv('updated_data.csv')
567
+
568
+ # Find scheme by slug
569
+ scheme_row = df[df['slug'] == slug]
570
+
571
+ if scheme_row.empty:
572
+ raise HTTPException(status_code=404, detail="Scheme not found")
573
+
574
+ scheme_row = scheme_row.iloc[0]
575
+
576
+ # Prepare scheme details
577
+ scheme_data = {
578
+ "scheme_name": str(scheme_row.get('scheme_name', '')),
579
+ "slug": str(scheme_row.get('slug', '')),
580
+ "details": str(scheme_row.get('details', '')),
581
+ "benefits": str(scheme_row.get('benefits', '')),
582
+ "eligibility": str(scheme_row.get('eligibility', '')),
583
+ "application": str(scheme_row.get('application', '')),
584
+ "documents": str(scheme_row.get('documents', '')),
585
+ "level": str(scheme_row.get('level', '')),
586
+ "scheme_category": str(scheme_row.get('schemeCategory', '')),
587
+ "tags": str(scheme_row.get('tags', ''))
588
+ }
589
+
590
+ # Translate if needed
591
+ if language != 'en' and language in SUPPORTED_LANGUAGES:
592
+ print(f"Translating scheme details to {SUPPORTED_LANGUAGES[language]}...")
593
+ scheme_data['scheme_name'] = TranslationService.translate_text(
594
+ scheme_data['scheme_name'], 'en', language
595
+ )
596
+ scheme_data['details'] = TranslationService.translate_text(
597
+ scheme_data['details'], 'en', language
598
+ )
599
+ scheme_data['benefits'] = TranslationService.translate_text(
600
+ scheme_data['benefits'], 'en', language
601
+ )
602
+ scheme_data['eligibility'] = TranslationService.translate_text(
603
+ scheme_data['eligibility'], 'en', language
604
+ )
605
+
606
+ return scheme_data
607
+
608
+ except HTTPException:
609
+ raise
610
+ except Exception as e:
611
+ print(f"Error fetching scheme: {str(e)}")
612
+ raise HTTPException(status_code=500, detail=f"Error fetching scheme: {str(e)}")
613
+
614
+ @app.get("/schemes/categories")
615
+ async def get_scheme_categories():
616
+ """
617
+ Get all unique scheme categories available
618
+
619
+ Returns list of all unique categories from the database.
620
+ """
621
+ try:
622
+ df = pd.read_csv('updated_data.csv')
623
+
624
+ # Get unique categories (may contain comma-separated values)
625
+ all_categories = set()
626
+ for cat in df['schemeCategory'].dropna():
627
+ # Split by comma and strip whitespace
628
+ categories = [c.strip() for c in str(cat).split(',')]
629
+ all_categories.update(categories)
630
+
631
+ return {
632
+ "categories": sorted(list(all_categories))
633
+ }
634
+
635
+ except Exception as e:
636
+ print(f"Error fetching categories: {str(e)}")
637
+ raise HTTPException(status_code=500, detail=f"Error fetching categories: {str(e)}")
638
+
639
+ @app.get("/schemes/stats")
640
+ async def get_scheme_statistics():
641
+ """
642
+ Get statistics about schemes in the database
643
+
644
+ Returns:
645
+ - Total number of schemes
646
+ - Count by level (Central/State)
647
+ - Count by categories
648
+ - Count by states
649
+ """
650
+ try:
651
+ df = pd.read_csv('updated_data.csv')
652
+
653
+ # Total schemes
654
+ total = len(df)
655
+
656
+ # Count by level
657
+ level_counts = df['level'].value_counts().to_dict()
658
+
659
+ # Count by category
660
+ category_counts = {}
661
+ for cat in df['schemeCategory'].dropna():
662
+ categories = [c.strip() for c in str(cat).split(',')]
663
+ for c in categories:
664
+ category_counts[c] = category_counts.get(c, 0) + 1
665
+
666
+ return {
667
+ "total_schemes": total,
668
+ "by_level": level_counts,
669
+ "by_category": dict(sorted(category_counts.items(), key=lambda x: x[1], reverse=True)[:10]),
670
+ "total_categories": len(category_counts)
671
+ }
672
+
673
+ except Exception as e:
674
+ print(f"Error fetching statistics: {str(e)}")
675
+ raise HTTPException(status_code=500, detail=f"Error fetching statistics: {str(e)}")
676
+
677
+ # Launch the app
678
+ if __name__ == "__main__":
679
+ print("\n๐ŸŒ Starting FastAPI server...")
680
+ print("๐Ÿ“– API Documentation: http://127.0.0.1:7860/docs")
681
+ print("๐Ÿ“Š Alternative docs: http://127.0.0.1:7860/redoc")
682
+ uvicorn.run(app, host="0.0.0.0", port=7860)