Amodit commited on
Commit
865e378
·
1 Parent(s): 0fc97b8

changed the fastapi file

Browse files
Files changed (1) hide show
  1. main_fastapi.py +385 -60
main_fastapi.py CHANGED
@@ -3,60 +3,165 @@
3
  import os
4
  import uuid
5
  import tempfile
6
- from fastapi import FastAPI, UploadFile, File, HTTPException
7
- from fastapi.responses import StreamingResponse
8
- from pydantic import BaseModel
 
 
 
9
  import io
 
10
 
11
  # --- Import all our backend logic and agents ---
12
  from agents.legal_agent import legal_agent
13
  from agents.scheme_chatbot import scheme_chatbot
14
  from agents.demystifier_agent import process_document_for_demystification
 
15
  from utils.pdf_generator import generate_formatted_pdf
16
 
17
  # --- 1. Initialize FastAPI App ---
18
  app = FastAPI(
19
- title="Jan-Contract API",
20
- description="A comprehensive API for generating digital contracts, finding government schemes, and analyzing legal documents for India's informal workforce.",
21
- version="1.0.0",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  )
23
 
24
- # --- 2. Pydantic Models for Request Bodies ---
25
- # These models provide automatic data validation and documentation for our API.
26
- class ContractRequest(BaseModel):
27
- user_request: str
 
 
 
 
28
 
 
 
 
 
29
  class SchemeRequest(BaseModel):
30
- user_profile: str
31
-
32
  class ChatRequest(BaseModel):
33
- session_id: str
34
- question: str
 
 
 
 
 
 
 
35
 
36
- # --- 3. State Management for the Demystifier Chat ---
37
- # This is a simple in-memory cache for a hackathon. For production, you would
38
- # use a more robust cache like Redis.
 
 
 
 
 
 
 
 
 
 
 
39
  SESSION_CACHE = {}
 
40
 
41
- # --- 4. API Endpoints ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- @app.post("/generate-contract/json", tags=["Contract Generator"])
44
- async def generate_contract_json(request: ContractRequest):
45
  """
46
- Takes a plain-text description and returns a structured JSON object
47
- containing the generated contract text (in Markdown) and relevant legal trivia.
48
  """
49
  try:
50
  result = legal_agent.invoke({"user_request": request.user_request})
51
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  except Exception as e:
53
- raise HTTPException(status_code=500, detail=f"An error occurred: {e}")
54
 
55
- @app.post("/generate-contract/pdf", tags=["Contract Generator"])
56
  async def generate_contract_pdf(request: ContractRequest):
57
  """
58
- Takes a plain-text description, generates a contract, and returns it
59
- directly as a downloadable PDF file.
60
  """
61
  try:
62
  result = legal_agent.invoke({"user_request": request.user_request})
@@ -67,67 +172,287 @@ async def generate_contract_pdf(request: ContractRequest):
67
  return StreamingResponse(
68
  io.BytesIO(pdf_bytes),
69
  media_type="application/pdf",
70
- headers={"Content-Disposition": "attachment;filename=digital_agreement.pdf"}
71
  )
72
  except Exception as e:
73
- raise HTTPException(status_code=500, detail=f"An error occurred: {e}")
74
 
75
- @app.post("/find-schemes", tags=["Scheme Finder"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  async def find_schemes(request: SchemeRequest):
77
  """
78
- Takes a user profile description and returns a list of relevant
79
- government schemes with names, descriptions, and official links.
80
  """
81
  try:
82
  response = scheme_chatbot.invoke({"user_profile": request.user_profile})
83
- return response
 
 
 
 
84
  except Exception as e:
85
- raise HTTPException(status_code=500, detail=f"An error occurred: {e}")
 
 
86
 
87
- @app.post("/demystify/upload", tags=["Document Demystifier"])
88
  async def demystify_upload(file: UploadFile = File(...)):
89
  """
90
- Upload a PDF document for analysis. This endpoint processes the document,
91
- creates a RAG chain for chatting, and returns the initial analysis report
92
- along with a unique `session_id` for follow-up questions.
93
  """
94
  if file.content_type != "application/pdf":
95
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
 
 
 
96
 
97
  try:
98
- # Use a temporary file to save the upload, as our loader needs a file path
99
- with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
100
- tmp.write(await file.read())
101
- tmp_path = tmp.name
102
 
103
- analysis_result = process_document_for_demystification(tmp_path)
 
 
104
 
105
- # Clean up the temporary file
106
- os.unlink(tmp_path)
107
-
108
- # Create a unique session ID and cache the RAG chain
109
  session_id = str(uuid.uuid4())
110
- SESSION_CACHE[session_id] = analysis_result["rag_chain"]
111
-
112
- return {
113
- "session_id": session_id,
114
- "report": analysis_result["report"]
115
  }
 
 
 
 
 
 
 
 
 
 
116
  except Exception as e:
117
- raise HTTPException(status_code=500, detail=f"Failed to process document: {e}")
118
 
119
- @app.post("/demystify/chat", tags=["Document Demystifier"])
120
  async def demystify_chat(request: ChatRequest):
121
  """
122
- Ask a follow-up question to a previously uploaded document.
123
- Requires the `session_id` returned by the /demystify/upload endpoint.
124
  """
125
- rag_chain = SESSION_CACHE.get(request.session_id)
126
- if not rag_chain:
127
  raise HTTPException(status_code=404, detail="Session not found. Please upload the document again.")
128
 
129
  try:
 
130
  response = rag_chain.invoke(request.question)
131
- return {"answer": response}
 
 
 
 
 
132
  except Exception as e:
133
- raise HTTPException(status_code=500, detail=f"An error occurred during chat: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import os
4
  import uuid
5
  import tempfile
6
+ import json
7
+ from typing import Optional, List
8
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks
9
+ from fastapi.responses import StreamingResponse, JSONResponse
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+ from pydantic import BaseModel, Field
12
  import io
13
+ import shutil
14
 
15
  # --- Import all our backend logic and agents ---
16
  from agents.legal_agent import legal_agent
17
  from agents.scheme_chatbot import scheme_chatbot
18
  from agents.demystifier_agent import process_document_for_demystification
19
+ from agents.general_assistant_agent import ask_gemini
20
  from utils.pdf_generator import generate_formatted_pdf
21
 
22
  # --- 1. Initialize FastAPI App ---
23
  app = FastAPI(
24
+ title="Jan-Contract Unified API",
25
+ description="""
26
+ A comprehensive API for India's informal workforce providing:
27
+
28
+ 🏗️ **Contract Generation**: Create digital agreements from plain text
29
+ 🏦 **Scheme Discovery**: Find relevant government schemes and benefits
30
+ 📜 **Document Analysis**: Demystify legal documents with AI-powered insights
31
+ 🤖 **General Assistant**: AI-powered guidance and support
32
+ 🎥 **Media Processing**: Audio/video consent recording and processing
33
+
34
+ Built with FastAPI, LangChain, and modern AI technologies.
35
+ """,
36
+ version="2.0.0",
37
+ contact={
38
+ "name": "Jan-Contract Team",
39
+ "email": "support@jan-contract.com"
40
+ },
41
+ license_info={
42
+ "name": "MIT License",
43
+ "url": "https://opensource.org/licenses/MIT"
44
+ }
45
  )
46
 
47
+ # --- 2. CORS Middleware ---
48
+ app.add_middleware(
49
+ CORSMiddleware,
50
+ allow_origins=["*"], # Configure appropriately for production
51
+ allow_credentials=True,
52
+ allow_methods=["*"],
53
+ allow_headers=["*"],
54
+ )
55
 
56
+ # --- 3. Pydantic Models for Request Bodies ---
57
+ class ContractRequest(BaseModel):
58
+ user_request: str = Field(..., description="Plain text description of the agreement needed", min_length=10)
59
+
60
  class SchemeRequest(BaseModel):
61
+ user_profile: str = Field(..., description="Description of user's situation, needs, or profile", min_length=10)
62
+
63
  class ChatRequest(BaseModel):
64
+ session_id: str = Field(..., description="Unique session identifier for document chat")
65
+ question: str = Field(..., description="Question about the uploaded document", min_length=1)
66
+
67
+ class GeneralChatRequest(BaseModel):
68
+ question: str = Field(..., description="General question for AI assistant", min_length=1)
69
+
70
+ class VideoConsentRequest(BaseModel):
71
+ contract_id: str = Field(..., description="Identifier for the contract this consent applies to")
72
+ consent_text: str = Field(..., description="Text of the consent being recorded", min_length=1)
73
 
74
+ # --- 4. Response Models ---
75
+ class ApiResponse(BaseModel):
76
+ success: bool
77
+ message: str
78
+ data: Optional[dict] = None
79
+ error: Optional[str] = None
80
+
81
+ class HealthCheck(BaseModel):
82
+ status: str
83
+ version: str
84
+ timestamp: str
85
+ services: dict
86
+
87
+ # --- 5. State Management ---
88
  SESSION_CACHE = {}
89
+ CONTRACT_CACHE = {}
90
 
91
+ # --- 6. Health Check Endpoint ---
92
+ @app.get("/health", tags=["System"], response_model=HealthCheck)
93
+ async def health_check():
94
+ """Check the health status of the API and its dependencies"""
95
+ import datetime
96
+
97
+ # Check if required directories exist
98
+ directories = {
99
+ "video_consents": os.path.exists("video_consents"),
100
+ "pdfs_demystify": os.path.exists("pdfs_demystify")
101
+ }
102
+
103
+ # Check if required modules can be imported
104
+ modules = {}
105
+ try:
106
+ import streamlit_webrtc
107
+ modules["streamlit_webrtc"] = "✅"
108
+ except:
109
+ modules["streamlit_webrtc"] = "❌"
110
+
111
+ try:
112
+ import av
113
+ modules["av"] = "✅"
114
+ except:
115
+ modules["av"] = "❌"
116
+
117
+ try:
118
+ import speech_recognition
119
+ modules["speech_recognition"] = "✅"
120
+ except:
121
+ modules["speech_recognition"] = "❌"
122
+
123
+ return HealthCheck(
124
+ status="healthy",
125
+ version="2.0.0",
126
+ timestamp=datetime.datetime.now().isoformat(),
127
+ services={
128
+ "directories": directories,
129
+ "modules": modules
130
+ }
131
+ )
132
+
133
+ # --- 7. Contract Generation Endpoints ---
134
 
135
+ @app.post("/contract/generate", tags=["Contract Generator"], response_model=ApiResponse)
136
+ async def generate_contract(request: ContractRequest):
137
  """
138
+ Generate a digital contract from plain text description.
139
+ Returns structured JSON with contract text and legal trivia.
140
  """
141
  try:
142
  result = legal_agent.invoke({"user_request": request.user_request})
143
+
144
+ # Cache the contract for later use
145
+ contract_id = str(uuid.uuid4())
146
+ CONTRACT_CACHE[contract_id] = result
147
+
148
+ return ApiResponse(
149
+ success=True,
150
+ message="Contract generated successfully",
151
+ data={
152
+ "contract_id": contract_id,
153
+ "contract": result.get('legal_doc', ''),
154
+ "legal_trivia": result.get('legal_trivia', {}),
155
+ "timestamp": str(uuid.uuid4())
156
+ }
157
+ )
158
  except Exception as e:
159
+ raise HTTPException(status_code=500, detail=f"Contract generation failed: {str(e)}")
160
 
161
+ @app.post("/contract/generate-pdf", tags=["Contract Generator"])
162
  async def generate_contract_pdf(request: ContractRequest):
163
  """
164
+ Generate a contract and return it as a downloadable PDF file.
 
165
  """
166
  try:
167
  result = legal_agent.invoke({"user_request": request.user_request})
 
172
  return StreamingResponse(
173
  io.BytesIO(pdf_bytes),
174
  media_type="application/pdf",
175
+ headers={"Content-Disposition": f"attachment;filename=digital_agreement_{uuid.uuid4()}.pdf"}
176
  )
177
  except Exception as e:
178
+ raise HTTPException(status_code=500, detail=f"PDF generation failed: {str(e)}")
179
 
180
+ @app.get("/contract/{contract_id}", tags=["Contract Generator"], response_model=ApiResponse)
181
+ async def get_contract(contract_id: str):
182
+ """Retrieve a previously generated contract by ID"""
183
+ if contract_id not in CONTRACT_CACHE:
184
+ raise HTTPException(status_code=404, detail="Contract not found")
185
+
186
+ return ApiResponse(
187
+ success=True,
188
+ message="Contract retrieved successfully",
189
+ data=CONTRACT_CACHE[contract_id]
190
+ )
191
+
192
+ # --- 8. Scheme Finder Endpoints ---
193
+
194
+ @app.post("/schemes/find", tags=["Scheme Finder"], response_model=ApiResponse)
195
  async def find_schemes(request: SchemeRequest):
196
  """
197
+ Find relevant government schemes based on user profile.
198
+ Returns list of schemes with descriptions and official links.
199
  """
200
  try:
201
  response = scheme_chatbot.invoke({"user_profile": request.user_profile})
202
+ return ApiResponse(
203
+ success=True,
204
+ message="Schemes found successfully",
205
+ data=response
206
+ )
207
  except Exception as e:
208
+ raise HTTPException(status_code=500, detail=f"Scheme search failed: {str(e)}")
209
+
210
+ # --- 9. Document Demystifier Endpoints ---
211
 
212
+ @app.post("/demystify/upload", tags=["Document Demystifier"], response_model=ApiResponse)
213
  async def demystify_upload(file: UploadFile = File(...)):
214
  """
215
+ Upload a PDF document for AI-powered analysis.
216
+ Returns analysis report and session ID for follow-up questions.
 
217
  """
218
  if file.content_type != "application/pdf":
219
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
220
+
221
+ if file.size and file.size > 50 * 1024 * 1024: # 50MB limit
222
+ raise HTTPException(status_code=400, detail="File too large. Maximum size is 50MB.")
223
 
224
  try:
225
+ # Save to project directory
226
+ upload_dir = "pdfs_demystify"
227
+ os.makedirs(upload_dir, exist_ok=True)
 
228
 
229
+ file_path = os.path.join(upload_dir, f"{uuid.uuid4()}_{file.filename}")
230
+ with open(file_path, "wb") as buffer:
231
+ shutil.copyfileobj(file.file, buffer)
232
 
233
+ # Process the document
234
+ analysis_result = process_document_for_demystification(file_path)
235
+
236
+ # Create session and cache RAG chain
237
  session_id = str(uuid.uuid4())
238
+ SESSION_CACHE[session_id] = {
239
+ "rag_chain": analysis_result["rag_chain"],
240
+ "file_path": file_path,
241
+ "upload_time": str(uuid.uuid4())
 
242
  }
243
+
244
+ return ApiResponse(
245
+ success=True,
246
+ message="Document uploaded and analyzed successfully",
247
+ data={
248
+ "session_id": session_id,
249
+ "report": analysis_result["report"],
250
+ "filename": file.filename
251
+ }
252
+ )
253
  except Exception as e:
254
+ raise HTTPException(status_code=500, detail=f"Document processing failed: {str(e)}")
255
 
256
+ @app.post("/demystify/chat", tags=["Document Demystifier"], response_model=ApiResponse)
257
  async def demystify_chat(request: ChatRequest):
258
  """
259
+ Ask follow-up questions about an uploaded document.
260
+ Requires valid session ID from upload endpoint.
261
  """
262
+ session_data = SESSION_CACHE.get(request.session_id)
263
+ if not session_data:
264
  raise HTTPException(status_code=404, detail="Session not found. Please upload the document again.")
265
 
266
  try:
267
+ rag_chain = session_data["rag_chain"]
268
  response = rag_chain.invoke(request.question)
269
+
270
+ return ApiResponse(
271
+ success=True,
272
+ message="Question answered successfully",
273
+ data={"answer": response}
274
+ )
275
  except Exception as e:
276
+ raise HTTPException(status_code=500, detail=f"Chat processing failed: {str(e)}")
277
+
278
+ # --- 10. General Assistant Endpoints ---
279
+
280
+ @app.post("/assistant/chat", tags=["General Assistant"], response_model=ApiResponse)
281
+ async def general_chat(request: GeneralChatRequest):
282
+ """
283
+ Get AI-powered assistance for general questions.
284
+ Uses Gemini AI model for responses.
285
+ """
286
+ try:
287
+ response = ask_gemini(request.question)
288
+ return ApiResponse(
289
+ success=True,
290
+ message="Response generated successfully",
291
+ data={"response": response}
292
+ )
293
+ except Exception as e:
294
+ raise HTTPException(status_code=500, detail=f"AI response generation failed: {str(e)}")
295
+
296
+ # --- 11. Media Processing Endpoints ---
297
+
298
+ @app.post("/media/upload-video", tags=["Media Processing"], response_model=ApiResponse)
299
+ async def upload_video_consent(
300
+ file: UploadFile = File(...),
301
+ contract_id: str = Form(...),
302
+ consent_text: str = Form(...)
303
+ ):
304
+ """
305
+ Upload a video consent file for a specific contract.
306
+ Supports MP4, AVI, MOV formats.
307
+ """
308
+ allowed_types = ["video/mp4", "video/avi", "video/quicktime", "video/x-msvideo"]
309
+
310
+ if file.content_type not in allowed_types:
311
+ raise HTTPException(
312
+ status_code=400,
313
+ detail=f"Invalid video format. Allowed: {', '.join(allowed_types)}"
314
+ )
315
+
316
+ if file.size and file.size > 100 * 1024 * 1024: # 100MB limit
317
+ raise HTTPException(status_code=400, detail="Video too large. Maximum size is 100MB.")
318
+
319
+ try:
320
+ # Save video to project directory
321
+ upload_dir = "video_consents"
322
+ os.makedirs(upload_dir, exist_ok=True)
323
+
324
+ video_filename = f"consent_{contract_id}_{uuid.uuid4()}.mp4"
325
+ video_path = os.path.join(upload_dir, video_filename)
326
+
327
+ with open(video_path, "wb") as buffer:
328
+ shutil.copyfileobj(file.file, buffer)
329
+
330
+ return ApiResponse(
331
+ success=True,
332
+ message="Video consent uploaded successfully",
333
+ data={
334
+ "video_path": video_path,
335
+ "contract_id": contract_id,
336
+ "filename": video_filename,
337
+ "size": file.size
338
+ }
339
+ )
340
+ except Exception as e:
341
+ raise HTTPException(status_code=500, detail=f"Video upload failed: {str(e)}")
342
+
343
+ @app.get("/media/videos/{contract_id}", tags=["Media Processing"], response_model=ApiResponse)
344
+ async def get_contract_videos(contract_id: str):
345
+ """Get all video consents for a specific contract"""
346
+ try:
347
+ video_dir = "video_consents"
348
+ if not os.path.exists(video_dir):
349
+ return ApiResponse(
350
+ success=True,
351
+ message="No videos found",
352
+ data={"videos": []}
353
+ )
354
+
355
+ videos = []
356
+ for filename in os.listdir(video_dir):
357
+ if filename.startswith(f"consent_{contract_id}_"):
358
+ file_path = os.path.join(video_dir, filename)
359
+ videos.append({
360
+ "filename": filename,
361
+ "path": file_path,
362
+ "size": os.path.getsize(file_path),
363
+ "created": str(uuid.uuid4())
364
+ })
365
+
366
+ return ApiResponse(
367
+ success=True,
368
+ message=f"Found {len(videos)} video(s) for contract",
369
+ data={"videos": videos}
370
+ )
371
+ except Exception as e:
372
+ raise HTTPException(status_code=500, detail=f"Video retrieval failed: {str(e)}")
373
+
374
+ # --- 12. Utility Endpoints ---
375
+
376
+ @app.get("/contracts", tags=["Utilities"], response_model=ApiResponse)
377
+ async def list_contracts():
378
+ """List all generated contracts"""
379
+ contracts = []
380
+ for contract_id, contract_data in CONTRACT_CACHE.items():
381
+ contracts.append({
382
+ "id": contract_id,
383
+ "summary": contract_data.get('legal_doc', '')[:100] + "...",
384
+ "timestamp": str(uuid.uuid4())
385
+ })
386
+
387
+ return ApiResponse(
388
+ success=True,
389
+ message=f"Found {len(contracts)} contract(s)",
390
+ data={"contracts": contracts}
391
+ )
392
+
393
+ @app.delete("/contracts/{contract_id}", tags=["Utilities"], response_model=ApiResponse)
394
+ async def delete_contract(contract_id: str):
395
+ """Delete a specific contract and its associated data"""
396
+ if contract_id not in CONTRACT_CACHE:
397
+ raise HTTPException(status_code=404, detail="Contract not found")
398
+
399
+ # Remove contract
400
+ del CONTRACT_CACHE[contract_id]
401
+
402
+ # Remove associated videos
403
+ video_dir = "video_consents"
404
+ if os.path.exists(video_dir):
405
+ for filename in os.listdir(video_dir):
406
+ if filename.startswith(f"consent_{contract_id}_"):
407
+ os.remove(os.path.join(video_dir, filename))
408
+
409
+ return ApiResponse(
410
+ success=True,
411
+ message="Contract and associated data deleted successfully"
412
+ )
413
+
414
+ @app.get("/", tags=["System"])
415
+ async def root():
416
+ """API root endpoint with basic information"""
417
+ return {
418
+ "message": "Jan-Contract Unified API",
419
+ "version": "2.0.0",
420
+ "description": "Comprehensive API for India's informal workforce",
421
+ "endpoints": {
422
+ "health": "/health",
423
+ "contracts": "/contract/generate",
424
+ "schemes": "/schemes/find",
425
+ "demystify": "/demystify/upload",
426
+ "assistant": "/assistant/chat",
427
+ "media": "/media/upload-video"
428
+ },
429
+ "docs": "/docs"
430
+ }
431
+
432
+ # --- 13. Error Handlers ---
433
+
434
+ @app.exception_handler(HTTPException)
435
+ async def http_exception_handler(request, exc):
436
+ return JSONResponse(
437
+ status_code=exc.status_code,
438
+ content=ApiResponse(
439
+ success=False,
440
+ message="Request failed",
441
+ error=str(exc.detail)
442
+ ).dict()
443
+ )
444
+
445
+ @app.exception_handler(Exception)
446
+ async def general_exception_handler(request, exc):
447
+ return JSONResponse(
448
+ status_code=500,
449
+ content=ApiResponse(
450
+ success=False,
451
+ message="Internal server error",
452
+ error="An unexpected error occurred"
453
+ ).dict()
454
+ )
455
+
456
+ if __name__ == "__main__":
457
+ import uvicorn
458
+ uvicorn.run(app, host="0.0.0.0", port=8000)