quantumbit commited on
Commit
a022b57
·
verified ·
1 Parent(s): c1a4784

Delete api

Browse files
Files changed (2) hide show
  1. api/__init__.py +0 -1
  2. api/api.py +0 -498
api/__init__.py DELETED
@@ -1 +0,0 @@
1
- # API Package
 
 
api/api.py DELETED
@@ -1,498 +0,0 @@
1
- from fastapi import FastAPI, HTTPException, Depends, Query
2
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
- from pydantic import BaseModel, HttpUrl
4
- from typing import List, Dict, Any, Optional
5
- import tempfile
6
- import os
7
- import hashlib
8
- import asyncio
9
- import aiohttp
10
- import time
11
- from contextlib import asynccontextmanager
12
-
13
- from RAG.advanced_rag_processor import AdvancedRAGProcessor
14
- from preprocessing.preprocessing import DocumentPreprocessor
15
- from logger.logger import rag_logger
16
- from LLM.llm_handler import llm_handler
17
- from LLM.tabular_answer import get_answer_for_tabluar
18
- from LLM.image_answerer import get_answer_for_image
19
- from LLM.one_shotter import get_oneshot_answer
20
- from config.config import *
21
- import config.config as config
22
-
23
- # Initialize security
24
- security = HTTPBearer()
25
- admin_security = HTTPBearer()
26
-
27
- def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
28
- """Verify the bearer token for main API."""
29
- if credentials.credentials != BEARER_TOKEN:
30
- raise HTTPException(
31
- status_code=401,
32
- detail="Invalid authentication token"
33
- )
34
- return credentials.credentials
35
-
36
- def verify_admin_token(credentials: HTTPAuthorizationCredentials = Depends(admin_security)):
37
- """Verify the bearer token for admin endpoints."""
38
- if credentials.credentials != "9420689497":
39
- raise HTTPException(
40
- status_code=401,
41
- detail="Invalid admin authentication token"
42
- )
43
- return credentials.credentials
44
-
45
- # Pydantic models for request/response
46
- class ProcessDocumentRequest(BaseModel):
47
- documents: HttpUrl # URL to the PDF document
48
- questions: List[str] # List of questions to answer
49
-
50
- class ProcessDocumentResponse(BaseModel):
51
- answers: List[str]
52
-
53
- class HealthResponse(BaseModel):
54
- status: str
55
- message: str
56
-
57
- class PreprocessingResponse(BaseModel):
58
- status: str
59
- message: str
60
- doc_id: str
61
- chunk_count: int
62
-
63
- class LogsResponse(BaseModel):
64
- export_timestamp: str
65
- metadata: Dict[str, Any]
66
- logs: List[Dict[str, Any]]
67
-
68
- class LogsSummaryResponse(BaseModel):
69
- summary: Dict[str, Any]
70
-
71
- # Global instances
72
- rag_processor: Optional[AdvancedRAGProcessor] = None
73
- document_preprocessor: Optional[DocumentPreprocessor] = None
74
-
75
- @asynccontextmanager
76
- async def lifespan(app: FastAPI):
77
- """Initialize and cleanup the RAG processor."""
78
- global rag_processor, document_preprocessor
79
-
80
- # Startup
81
- print("🚀 Initializing Advanced RAG System...")
82
- rag_processor = AdvancedRAGProcessor() # Use advanced processor for better accuracy
83
- document_preprocessor = DocumentPreprocessor()
84
- print("✅ Advanced RAG System initialized successfully")
85
-
86
- yield
87
-
88
- # Shutdown
89
- print("🔄 Shutting down RAG System...")
90
- if rag_processor:
91
- rag_processor.cleanup()
92
- print("✅ Cleanup completed")
93
-
94
- # FastAPI app with lifespan management
95
- app = FastAPI(
96
- title="Advanced RAG API",
97
- description="API for document processing and question answering using RAG",
98
- version="1.0.0",
99
- lifespan=lifespan
100
- )
101
-
102
- @app.get("/health", response_model=HealthResponse)
103
- async def health_check():
104
- """Health check endpoint."""
105
- return HealthResponse(
106
- status="healthy",
107
- message="RAG API is running successfully"
108
- )
109
-
110
- @app.post("/hackrx/run", response_model=ProcessDocumentResponse)
111
- async def process_document(
112
- request: ProcessDocumentRequest,
113
- token: str = Depends(verify_token)
114
- ):
115
- """
116
- Process a PDF document and answer questions about it.
117
-
118
- This endpoint implements an optimized flow:
119
- 1. Check if the document is already processed (pre-computed embeddings)
120
- 2. If yes, use existing embeddings for fast retrieval + generation
121
- 3. If no, run full RAG pipeline (download + process + embed + store + answer)
122
-
123
- Args:
124
- request: Contains document URL and list of questions
125
- token: Bearer token for authentication
126
-
127
- Returns:
128
- ProcessDocumentResponse: List of answers corresponding to the questions
129
- """
130
- global rag_processor, document_preprocessor
131
-
132
- if not rag_processor or not document_preprocessor:
133
- raise HTTPException(
134
- status_code=503,
135
- detail="RAG system not initialized"
136
- )
137
-
138
- # Start timing and logging
139
- start_time = time.time()
140
- document_url = str(request.documents)
141
- questions = request.questions
142
- # Initialize answers for safe logging in finally
143
- final_answers = []
144
- status = "success"
145
- error_message = None
146
- doc_id = None
147
- was_preprocessed = False
148
-
149
- # Initialize enhanced logging
150
- request_id = rag_logger.generate_request_id()
151
- rag_logger.start_request_timing(request_id)
152
-
153
- try:
154
- print(f"📋 [{request_id}] Processing document: {document_url[:50]}...")
155
- print(f"🤔 [{request_id}] Number of questions: {len(questions)}")
156
- print(f"")
157
- print(f"🚀 [{request_id}] ===== STARTING RAG PIPELINE =====")
158
- print(f"📌 [{request_id}] PRIORITY 1: Checking stored embeddings database...")
159
-
160
- # Generate document ID
161
- doc_id = document_preprocessor.generate_doc_id(document_url)
162
-
163
- # Step 1: Check if document is already processed (stored embeddings)
164
- is_processed = document_preprocessor.is_document_processed(document_url)
165
- was_preprocessed = is_processed
166
-
167
- if is_processed:
168
- print(f"✅ [{request_id}] ✅ FOUND STORED EMBEDDINGS for {doc_id}")
169
- print(f"⚡ [{request_id}] Using fast path with pre-computed embeddings")
170
- # Fast path: Use existing embeddings
171
- doc_info = document_preprocessor.get_document_info(document_url)
172
- print(f"📊 [{request_id}] Using existing collection with {doc_info.get('chunk_count', 'N/A')} chunks")
173
- else:
174
- print(f"❌ [{request_id}] No stored embeddings found for {doc_id}")
175
- print(f"📌 [{request_id}] PRIORITY 2: Running full RAG pipeline (download + process + embed)...")
176
- # Full path: Download and process document
177
- resp = await document_preprocessor.process_document(document_url)
178
-
179
- # Handle different return formats: [content, type] or [content, type, no_cleanup_flag]
180
- if isinstance(resp, list):
181
- content, _type = resp[0], resp[1]
182
- if content == 'unsupported':
183
- # Unsupported file type: respond gracefully without throwing server error
184
- msg = f"Unsupported file type: {_type.lstrip('.')}"
185
- final_answers = [msg]
186
- status = "success" # ensure no 500 is raised in the finally block
187
- return ProcessDocumentResponse(answers=final_answers)
188
-
189
- if _type == "image":
190
- try:
191
- final_answers = get_answer_for_image(content, questions)
192
- status = "success"
193
- return ProcessDocumentResponse(answers=final_answers)
194
- finally:
195
- # Clean up the image file after processing
196
- if os.path.exists(content):
197
- os.unlink(content)
198
- print(f"🗑️ Cleaned up image file: {content}")
199
-
200
- if _type == "tabular":
201
- final_answers = get_answer_for_tabluar(content, questions)
202
- status = "success"
203
- return ProcessDocumentResponse(answers=final_answers)
204
-
205
- if _type == "oneshot":
206
- # Process questions in batches for oneshot
207
- tasks = [
208
- get_oneshot_answer(content, questions[i:i + 3])
209
- for i in range(0, len(questions), 3)
210
- ]
211
-
212
- # Run all batches in parallel
213
- results = await asyncio.gather(*tasks)
214
-
215
- # Flatten results
216
- final_answers = [ans for batch in results for ans in batch]
217
- status = "success"
218
- return ProcessDocumentResponse(answers=final_answers)
219
- else:
220
- doc_id = resp
221
-
222
- print(f"✅ [{request_id}] Document {doc_id} processed and stored")
223
-
224
- # Answer all questions using parallel processing for better latency
225
- print(f"🚀 [{request_id}] Processing {len(questions)} questions in parallel...")
226
-
227
- async def answer_single_question(question: str, index: int) -> tuple[str, Dict[str, float]]:
228
- """Answer a single question with error handling and timing."""
229
- try:
230
- question_start = time.time()
231
- print(f"❓ [{request_id}] Q{index+1}: {question[:50]}...")
232
-
233
- answer, pipeline_timings = await rag_processor.answer_question(
234
- question=question,
235
- doc_id=doc_id,
236
- logger=rag_logger,
237
- request_id=request_id
238
- )
239
-
240
- question_time = time.time() - question_start
241
-
242
- # Log question timing
243
- rag_logger.log_question_timing(
244
- request_id, index, question, answer, question_time, pipeline_timings
245
- )
246
-
247
- print(f"✅ [{request_id}] Q{index+1} completed in {question_time:.4f}s")
248
- return answer, pipeline_timings
249
- except Exception as e:
250
- print(f"❌ [{request_id}] Q{index+1} Error: {str(e)}")
251
- return f"I encountered an error while processing this question: {str(e)}", {}
252
-
253
-
254
- # Process questions in parallel with controlled concurrency
255
- semaphore = asyncio.Semaphore(3) # Reduced concurrency for better logging visibility
256
-
257
- async def bounded_answer(question: str, index: int) -> tuple[str, Dict[str, float]]:
258
- async with semaphore:
259
- return await answer_single_question(question, index)
260
-
261
- # Execute all questions concurrently
262
- tasks = [
263
- bounded_answer(question, i)
264
- for i, question in enumerate(questions)
265
- ]
266
-
267
- results = await asyncio.gather(*tasks, return_exceptions=True)
268
-
269
- # Handle any exceptions in answers
270
- final_answers = []
271
- error_count = 0
272
- for i, result in enumerate(results):
273
- if isinstance(result, Exception):
274
- error_count += 1
275
- final_answers.append(f"Error processing question {i+1}: {str(result)}")
276
- else:
277
- answer, _ = result
278
- final_answers.append(answer)
279
-
280
- # Determine final status
281
- if error_count == 0:
282
- status = "success"
283
- elif error_count == len(questions):
284
- status = "error"
285
- else:
286
- status = "partial"
287
-
288
- print(f"✅ [{request_id}] Successfully processed {len(questions) - error_count}/{len(questions)} questions")
289
-
290
- except Exception as e:
291
- print(f"❌ [{request_id}] Error processing request: {str(e)}")
292
- status = "error"
293
- error_message = str(e)
294
- final_answers = [f"Error: {str(e)}" for _ in questions]
295
-
296
- finally:
297
- # End request timing and get detailed timing data
298
- timing_data = rag_logger.end_request_timing(request_id)
299
-
300
- # Log the request with enhanced timing
301
- processing_time = time.time() - start_time
302
- logged_request_id = rag_logger.log_request(
303
- document_url=document_url,
304
- questions=questions,
305
- answers=final_answers,
306
- processing_time=processing_time,
307
- status=status,
308
- error_message=error_message,
309
- document_id=doc_id,
310
- was_preprocessed=was_preprocessed,
311
- timing_data=timing_data
312
- )
313
-
314
- print(f"📊 Request logged with ID: {logged_request_id} (Status: {status}, Time: {processing_time:.2f}s)")
315
-
316
- if status == "error":
317
- raise HTTPException(
318
- status_code=500,
319
- detail=f"Failed to process document: {error_message}"
320
- )
321
-
322
- return ProcessDocumentResponse(answers=final_answers)
323
-
324
- @app.post("/preprocess", response_model=PreprocessingResponse)
325
- async def preprocess_document(document_url: str, force: bool = False, token: str = Depends(verify_admin_token)):
326
- """
327
- Preprocess a document (for batch preprocessing).
328
-
329
- Args:
330
- document_url: URL of the PDF to preprocess
331
- force: Whether to reprocess if already processed
332
-
333
- Returns:
334
- PreprocessingResponse: Status and document info
335
- """
336
- global document_preprocessor
337
-
338
- if not document_preprocessor:
339
- raise HTTPException(
340
- status_code=503,
341
- detail="Document preprocessor not initialized"
342
- )
343
-
344
- try:
345
- doc_id = await document_preprocessor.process_document(document_url, force)
346
- doc_info = document_preprocessor.get_document_info(document_url)
347
-
348
- return PreprocessingResponse(
349
- status="success",
350
- message=f"Document processed successfully",
351
- doc_id=doc_id,
352
- chunk_count=doc_info.get("chunk_count", 0)
353
- )
354
-
355
- except Exception as e:
356
- raise HTTPException(
357
- status_code=500,
358
- detail=f"Failed to preprocess document: {str(e)}"
359
- )
360
-
361
- @app.get("/collections")
362
- async def list_collections(token: str = Depends(verify_admin_token)):
363
- """List all available document collections."""
364
- global document_preprocessor
365
-
366
- if not document_preprocessor:
367
- raise HTTPException(
368
- status_code=503,
369
- detail="Document preprocessor not initialized"
370
- )
371
-
372
- try:
373
- processed_docs = document_preprocessor.list_processed_documents()
374
- return {"collections": processed_docs}
375
- except Exception as e:
376
- raise HTTPException(
377
- status_code=500,
378
- detail=f"Failed to list collections: {str(e)}"
379
- )
380
-
381
- @app.get("/collections/stats")
382
- async def get_collection_stats(token: str = Depends(verify_admin_token)):
383
- """Get statistics about all collections."""
384
- global document_preprocessor
385
-
386
- if not document_preprocessor:
387
- raise HTTPException(
388
- status_code=503,
389
- detail="Document preprocessor not initialized"
390
- )
391
-
392
- try:
393
- stats = document_preprocessor.get_collection_stats()
394
- return stats
395
- except Exception as e:
396
- raise HTTPException(
397
- status_code=500,
398
- detail=f"Failed to get collection stats: {str(e)}"
399
- )
400
-
401
- # Logging Endpoints
402
- @app.get("/logs", response_model=LogsResponse)
403
- async def get_logs(
404
- token: str = Depends(verify_admin_token),
405
- limit: Optional[int] = Query(None, description="Maximum number of logs to return"),
406
- minutes: Optional[int] = Query(None, description="Get logs from last N minutes"),
407
- document_url: Optional[str] = Query(None, description="Filter logs by document URL")
408
- ):
409
- """
410
- Export all API request logs as JSON.
411
-
412
- Query Parameters:
413
- limit: Maximum number of recent logs to return
414
- minutes: Get logs from the last N minutes
415
- document_url: Filter logs for a specific document URL
416
-
417
- Returns:
418
- LogsResponse: Complete logs export with metadata
419
- """
420
- try:
421
- if document_url:
422
- # Get logs for specific document
423
- logs = rag_logger.get_logs_by_document(document_url)
424
- metadata = {
425
- "filtered_by": "document_url",
426
- "document_url": document_url,
427
- "total_logs": len(logs)
428
- }
429
- return LogsResponse(
430
- export_timestamp=rag_logger.export_logs()["export_timestamp"],
431
- metadata=metadata,
432
- logs=logs
433
- )
434
-
435
- elif minutes:
436
- # Get recent logs
437
- logs = rag_logger.get_recent_logs(minutes)
438
- metadata = {
439
- "filtered_by": "time_range",
440
- "minutes": minutes,
441
- "total_logs": len(logs)
442
- }
443
- return LogsResponse(
444
- export_timestamp=rag_logger.export_logs()["export_timestamp"],
445
- metadata=metadata,
446
- logs=logs
447
- )
448
-
449
- else:
450
- # Get all logs (with optional limit)
451
- if limit:
452
- logs = rag_logger.get_logs(limit)
453
- metadata = rag_logger.get_logs_summary()
454
- metadata["limited_to"] = limit
455
- else:
456
- logs_export = rag_logger.export_logs()
457
- return LogsResponse(**logs_export)
458
-
459
- return LogsResponse(
460
- export_timestamp=rag_logger.export_logs()["export_timestamp"],
461
- metadata=metadata,
462
- logs=logs
463
- )
464
-
465
- except Exception as e:
466
- raise HTTPException(
467
- status_code=500,
468
- detail=f"Failed to export logs: {str(e)}"
469
- )
470
-
471
- @app.get("/logs/summary", response_model=LogsSummaryResponse)
472
- async def get_logs_summary(token: str = Depends(verify_admin_token)):
473
- """
474
- Get summary statistics of all logs.
475
-
476
- Returns:
477
- LogsSummaryResponse: Summary statistics
478
- """
479
- try:
480
- summary = rag_logger.get_logs_summary()
481
- return LogsSummaryResponse(summary=summary)
482
- except Exception as e:
483
- raise HTTPException(
484
- status_code=500,
485
- detail=f"Failed to get logs summary: {str(e)}"
486
- )
487
-
488
- if __name__ == "__main__":
489
- import uvicorn
490
-
491
- # Run the FastAPI server
492
- uvicorn.run(
493
- "api:app",
494
- host=API_HOST,
495
- port=API_PORT,
496
- reload=API_RELOAD,
497
- log_level="info"
498
- )