Instructions to use Navaneeth-14/rag-hackathon-app with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use Navaneeth-14/rag-hackathon-app with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf Navaneeth-14/rag-hackathon-app:Q4_K_M # Run inference directly in the terminal: llama cli -hf Navaneeth-14/rag-hackathon-app:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf Navaneeth-14/rag-hackathon-app:Q4_K_M # Run inference directly in the terminal: llama cli -hf Navaneeth-14/rag-hackathon-app:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf Navaneeth-14/rag-hackathon-app:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf Navaneeth-14/rag-hackathon-app:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf Navaneeth-14/rag-hackathon-app:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf Navaneeth-14/rag-hackathon-app:Q4_K_M
Use Docker
docker model run hf.co/Navaneeth-14/rag-hackathon-app:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use Navaneeth-14/rag-hackathon-app with Ollama:
ollama run hf.co/Navaneeth-14/rag-hackathon-app:Q4_K_M
- Unsloth Studio
How to use Navaneeth-14/rag-hackathon-app with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Navaneeth-14/rag-hackathon-app to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Navaneeth-14/rag-hackathon-app to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for Navaneeth-14/rag-hackathon-app to start chatting
- Docker Model Runner
How to use Navaneeth-14/rag-hackathon-app with Docker Model Runner:
docker model run hf.co/Navaneeth-14/rag-hackathon-app:Q4_K_M
- Lemonade
How to use Navaneeth-14/rag-hackathon-app with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull Navaneeth-14/rag-hackathon-app:Q4_K_M
Run and chat with the model
lemonade run user.rag-hackathon-app-Q4_K_M
List all available models
lemonade list
- Atomic Chat
Navaneethakrishnan commited on
Commit ·
09281fe
1
Parent(s): d9ba19e
Add RAG system without large files
Browse files- API_README.md +374 -0
- API_SERVER_README.md +225 -0
- README.md +0 -3
- SIMPLE_API_README.md +192 -0
- app.py +691 -0
- app2.py +690 -0
- demo.py +419 -0
- doc2_processed_results.txt +0 -0
- document_processer.py +650 -0
- keep_alive.py +68 -0
- llm_reasoning.py +819 -0
- main.py +782 -0
- performance_analysis.py +155 -0
- query_parser.py +600 -0
- quick_integration_test.py +209 -0
- quick_test.py +60 -0
- rag_system.py +513 -0
- rag_system_gpp.py +633 -0
- startup.sh +2 -0
- test_api.py +138 -0
- test_app2.py +206 -0
- test_document_processing.py +149 -0
- test_document_to_vector.py +370 -0
- test_hackathon_format.py +137 -0
- test_hackrx.py +108 -0
- test_integrated_system.py +543 -0
- test_llm_reasoning.py +1 -0
- test_pdf_processor.py +345 -0
- test_query_parser.py +474 -0
- test_rag_system.py +367 -0
- test_upload.py +94 -0
- vector_database.py +567 -0
API_README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flask API Server for Advanced RAG System
|
| 2 |
+
|
| 3 |
+
This Flask API server provides REST endpoints for the Advanced RAG System, allowing you to upload documents and process queries programmatically.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Document Upload**: Upload and process various document formats (PDF, TXT, DOCX, HTML, etc.)
|
| 8 |
+
- **Query Processing**: Ask questions about uploaded documents
|
| 9 |
+
- **OCR Support**: Process scanned documents with OCR
|
| 10 |
+
- **System Management**: Check status, validate components, clear data
|
| 11 |
+
- **Authentication**: Bearer token authentication
|
| 12 |
+
- **Error Handling**: Comprehensive error handling and fallback mechanisms
|
| 13 |
+
|
| 14 |
+
## API Endpoints
|
| 15 |
+
|
| 16 |
+
### 1. Health Check
|
| 17 |
+
**GET** `/health`
|
| 18 |
+
|
| 19 |
+
Check if the server is running and healthy.
|
| 20 |
+
|
| 21 |
+
**Response:**
|
| 22 |
+
```json
|
| 23 |
+
{
|
| 24 |
+
"status": "healthy",
|
| 25 |
+
"rag_system_initialized": true,
|
| 26 |
+
"ocr_available": true
|
| 27 |
+
}
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### 2. System Status
|
| 31 |
+
**GET** `/hackrx/status`
|
| 32 |
+
|
| 33 |
+
Get detailed system status and statistics.
|
| 34 |
+
|
| 35 |
+
**Headers:**
|
| 36 |
+
```
|
| 37 |
+
Authorization: Bearer your_api_key_here
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
**Response:**
|
| 41 |
+
```json
|
| 42 |
+
{
|
| 43 |
+
"status": "ready",
|
| 44 |
+
"statistics": {
|
| 45 |
+
"vector_database": {
|
| 46 |
+
"total_chunks": 150,
|
| 47 |
+
"unique_sources": 3,
|
| 48 |
+
"file_types": ["pdf", "txt"]
|
| 49 |
+
},
|
| 50 |
+
"audit_trail": {
|
| 51 |
+
"total_entries": 25,
|
| 52 |
+
"successful_queries": 20,
|
| 53 |
+
"failed_queries": 5
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
"ocr_available": true
|
| 57 |
+
}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### 3. Document Upload
|
| 61 |
+
**POST** `/hackrx/upload`
|
| 62 |
+
|
| 63 |
+
Upload and process a document.
|
| 64 |
+
|
| 65 |
+
**Headers:**
|
| 66 |
+
```
|
| 67 |
+
Authorization: Bearer your_api_key_here
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
**Form Data:**
|
| 71 |
+
- `file`: The document file to upload
|
| 72 |
+
- `use_ocr`: (optional) "true" or "false" to enable OCR for PDFs
|
| 73 |
+
|
| 74 |
+
**Supported File Types:**
|
| 75 |
+
- PDF (.pdf)
|
| 76 |
+
- Text (.txt)
|
| 77 |
+
- Word (.docx)
|
| 78 |
+
- HTML (.html, .htm)
|
| 79 |
+
- Email (.eml, .msg)
|
| 80 |
+
- CSV (.csv)
|
| 81 |
+
- JSON (.json)
|
| 82 |
+
|
| 83 |
+
**Response:**
|
| 84 |
+
```json
|
| 85 |
+
{
|
| 86 |
+
"success": true,
|
| 87 |
+
"message": "Document processed successfully",
|
| 88 |
+
"chunks_processed": 45,
|
| 89 |
+
"processing_time": 2.34,
|
| 90 |
+
"filename": "document.pdf"
|
| 91 |
+
}
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### 4. Query Processing
|
| 95 |
+
**POST** `/hackrx/run`
|
| 96 |
+
|
| 97 |
+
Process questions about uploaded documents.
|
| 98 |
+
|
| 99 |
+
**Headers:**
|
| 100 |
+
```
|
| 101 |
+
Authorization: Bearer your_api_key_here
|
| 102 |
+
Content-Type: application/json
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
**Request Body:**
|
| 106 |
+
```json
|
| 107 |
+
{
|
| 108 |
+
"questions": [
|
| 109 |
+
"What is covered under this policy?",
|
| 110 |
+
"What is the maximum coverage amount?",
|
| 111 |
+
"What documents are required for claims?"
|
| 112 |
+
]
|
| 113 |
+
}
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
**Response:**
|
| 117 |
+
```json
|
| 118 |
+
{
|
| 119 |
+
"answers": [
|
| 120 |
+
{
|
| 121 |
+
"question": "What is covered under this policy?",
|
| 122 |
+
"answer": "Based on the policy document, the following are covered...",
|
| 123 |
+
"decision": "COVERED",
|
| 124 |
+
"confidence": 0.85,
|
| 125 |
+
"processing_time": 1.23,
|
| 126 |
+
"amount": 50000.0,
|
| 127 |
+
"waiting_period": "30 days",
|
| 128 |
+
"relevant_clauses": ["Section 3.1", "Section 4.2"],
|
| 129 |
+
"conditions": ["Must be hospitalized", "Pre-authorization required"],
|
| 130 |
+
"exclusions": ["Cosmetic procedures", "Experimental treatments"],
|
| 131 |
+
"required_documents": ["Hospital bills", "Medical reports"]
|
| 132 |
+
}
|
| 133 |
+
]
|
| 134 |
+
}
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### 5. System Validation
|
| 138 |
+
**GET** `/hackrx/validate`
|
| 139 |
+
|
| 140 |
+
Validate all system components.
|
| 141 |
+
|
| 142 |
+
**Headers:**
|
| 143 |
+
```
|
| 144 |
+
Authorization: Bearer your_api_key_here
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
**Response:**
|
| 148 |
+
```json
|
| 149 |
+
{
|
| 150 |
+
"document_processor": true,
|
| 151 |
+
"vector_database": true,
|
| 152 |
+
"query_parser": true,
|
| 153 |
+
"reasoning_engine": true,
|
| 154 |
+
"all_valid": true,
|
| 155 |
+
"errors": []
|
| 156 |
+
}
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### 6. Clear System
|
| 160 |
+
**POST** `/hackrx/clear`
|
| 161 |
+
|
| 162 |
+
Clear all system data and reset the RAG system.
|
| 163 |
+
|
| 164 |
+
**Headers:**
|
| 165 |
+
```
|
| 166 |
+
Authorization: Bearer your_api_key_here
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
**Response:**
|
| 170 |
+
```json
|
| 171 |
+
{
|
| 172 |
+
"success": true,
|
| 173 |
+
"message": "System cleared successfully"
|
| 174 |
+
}
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
## Authentication
|
| 178 |
+
|
| 179 |
+
All endpoints (except `/health`) require Bearer token authentication:
|
| 180 |
+
|
| 181 |
+
```
|
| 182 |
+
Authorization: Bearer your_api_key_here
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
**Default API Key:** `your_api_key_here`
|
| 186 |
+
|
| 187 |
+
**Note:** Change this in production for security.
|
| 188 |
+
|
| 189 |
+
## Error Responses
|
| 190 |
+
|
| 191 |
+
All endpoints return appropriate HTTP status codes:
|
| 192 |
+
|
| 193 |
+
- `200`: Success
|
| 194 |
+
- `400`: Bad Request (missing parameters, invalid data)
|
| 195 |
+
- `401`: Unauthorized (missing or invalid Authorization header)
|
| 196 |
+
- `403`: Forbidden (invalid API key)
|
| 197 |
+
- `500`: Internal Server Error
|
| 198 |
+
|
| 199 |
+
Error response format:
|
| 200 |
+
```json
|
| 201 |
+
{
|
| 202 |
+
"error": "Error description"
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
## Usage Examples
|
| 207 |
+
|
| 208 |
+
### Python Example
|
| 209 |
+
|
| 210 |
+
```python
|
| 211 |
+
import requests
|
| 212 |
+
import json
|
| 213 |
+
|
| 214 |
+
# Configuration
|
| 215 |
+
BASE_URL = "http://localhost:5000"
|
| 216 |
+
API_KEY = "your_api_key_here"
|
| 217 |
+
HEADERS = {
|
| 218 |
+
"Authorization": f"Bearer {API_KEY}",
|
| 219 |
+
"Content-Type": "application/json"
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
# 1. Upload a document
|
| 223 |
+
with open("document.pdf", "rb") as f:
|
| 224 |
+
files = {"file": f}
|
| 225 |
+
data = {"use_ocr": "false"}
|
| 226 |
+
upload_headers = {"Authorization": f"Bearer {API_KEY}"}
|
| 227 |
+
|
| 228 |
+
response = requests.post(
|
| 229 |
+
f"{BASE_URL}/hackrx/upload",
|
| 230 |
+
files=files,
|
| 231 |
+
data=data,
|
| 232 |
+
headers=upload_headers
|
| 233 |
+
)
|
| 234 |
+
print("Upload response:", response.json())
|
| 235 |
+
|
| 236 |
+
# 2. Process queries
|
| 237 |
+
questions = [
|
| 238 |
+
"What is covered under this policy?",
|
| 239 |
+
"What is the maximum coverage amount?"
|
| 240 |
+
]
|
| 241 |
+
|
| 242 |
+
payload = {"questions": questions}
|
| 243 |
+
response = requests.post(
|
| 244 |
+
f"{BASE_URL}/hackrx/run",
|
| 245 |
+
json=payload,
|
| 246 |
+
headers=HEADERS
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
answers = response.json()["answers"]
|
| 250 |
+
for answer in answers:
|
| 251 |
+
print(f"Q: {answer['question']}")
|
| 252 |
+
print(f"A: {answer['answer']}")
|
| 253 |
+
print(f"Decision: {answer['decision']}")
|
| 254 |
+
print(f"Confidence: {answer['confidence']}")
|
| 255 |
+
print("---")
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
### cURL Examples
|
| 259 |
+
|
| 260 |
+
**Health Check:**
|
| 261 |
+
```bash
|
| 262 |
+
curl http://localhost:5000/health
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
**System Status:**
|
| 266 |
+
```bash
|
| 267 |
+
curl -H "Authorization: Bearer your_api_key_here" \
|
| 268 |
+
http://localhost:5000/hackrx/status
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
**Upload Document:**
|
| 272 |
+
```bash
|
| 273 |
+
curl -X POST \
|
| 274 |
+
-H "Authorization: Bearer your_api_key_here" \
|
| 275 |
+
-F "file=@document.pdf" \
|
| 276 |
+
-F "use_ocr=false" \
|
| 277 |
+
http://localhost:5000/hackrx/upload
|
| 278 |
+
```
|
| 279 |
+
|
| 280 |
+
**Process Queries:**
|
| 281 |
+
```bash
|
| 282 |
+
curl -X POST \
|
| 283 |
+
-H "Authorization: Bearer your_api_key_here" \
|
| 284 |
+
-H "Content-Type: application/json" \
|
| 285 |
+
-d '{"questions": ["What is covered under this policy?"]}' \
|
| 286 |
+
http://localhost:5000/hackrx/run
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
## Running the Server
|
| 290 |
+
|
| 291 |
+
1. **Install Dependencies:**
|
| 292 |
+
```bash
|
| 293 |
+
pip install flask requests
|
| 294 |
+
```
|
| 295 |
+
|
| 296 |
+
2. **Start the Server:**
|
| 297 |
+
```bash
|
| 298 |
+
python app.py
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
3. **Test the API:**
|
| 302 |
+
```bash
|
| 303 |
+
python test_api.py
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
## Configuration
|
| 307 |
+
|
| 308 |
+
### Environment Variables
|
| 309 |
+
|
| 310 |
+
You can set these environment variables:
|
| 311 |
+
|
| 312 |
+
- `FLASK_ENV`: Set to `production` for production deployment
|
| 313 |
+
- `API_KEY`: Override the default API key
|
| 314 |
+
- `PORT`: Override the default port (5000)
|
| 315 |
+
|
| 316 |
+
### Production Deployment
|
| 317 |
+
|
| 318 |
+
For production deployment:
|
| 319 |
+
|
| 320 |
+
1. Change the API key in `app.py`
|
| 321 |
+
2. Set `debug=False` in `app.run()`
|
| 322 |
+
3. Use a production WSGI server like Gunicorn:
|
| 323 |
+
```bash
|
| 324 |
+
pip install gunicorn
|
| 325 |
+
gunicorn -w 4 -b 0.0.0.0:5000 app:app
|
| 326 |
+
```
|
| 327 |
+
|
| 328 |
+
## Troubleshooting
|
| 329 |
+
|
| 330 |
+
### Common Issues
|
| 331 |
+
|
| 332 |
+
1. **RAG System Initialization Failed**
|
| 333 |
+
- Check if all required dependencies are installed
|
| 334 |
+
- Ensure model files are available
|
| 335 |
+
- Check system memory and resources
|
| 336 |
+
|
| 337 |
+
2. **Document Upload Fails**
|
| 338 |
+
- Verify file format is supported
|
| 339 |
+
- Check file size limits
|
| 340 |
+
- Ensure proper file permissions
|
| 341 |
+
|
| 342 |
+
3. **Query Processing Errors**
|
| 343 |
+
- Make sure documents are uploaded first
|
| 344 |
+
- Check if the RAG system is properly initialized
|
| 345 |
+
- Verify the question format
|
| 346 |
+
|
| 347 |
+
4. **Authentication Errors**
|
| 348 |
+
- Ensure the Authorization header is present
|
| 349 |
+
- Verify the API key is correct
|
| 350 |
+
- Check the Bearer token format
|
| 351 |
+
|
| 352 |
+
### Logs
|
| 353 |
+
|
| 354 |
+
The server provides detailed logging. Check the console output for:
|
| 355 |
+
- RAG system initialization status
|
| 356 |
+
- Document processing progress
|
| 357 |
+
- Query processing results
|
| 358 |
+
- Error messages and stack traces
|
| 359 |
+
|
| 360 |
+
## Security Considerations
|
| 361 |
+
|
| 362 |
+
1. **Change the Default API Key**: Update `your_api_key_here` in production
|
| 363 |
+
2. **Use HTTPS**: Always use HTTPS in production
|
| 364 |
+
3. **Rate Limiting**: Consider implementing rate limiting for production use
|
| 365 |
+
4. **Input Validation**: The API includes basic validation, but add more as needed
|
| 366 |
+
5. **File Upload Security**: Implement additional file validation for production
|
| 367 |
+
|
| 368 |
+
## Support
|
| 369 |
+
|
| 370 |
+
For issues and questions:
|
| 371 |
+
1. Check the console logs for error messages
|
| 372 |
+
2. Verify all dependencies are installed
|
| 373 |
+
3. Test with the provided `test_api.py` script
|
| 374 |
+
4. Check the system validation endpoint for component status
|
API_SERVER_README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flask API Server for main.py
|
| 2 |
+
|
| 3 |
+
A simple Flask API server that provides REST endpoints for the main.py RAG system without duplicating model loading or initialization.
|
| 4 |
+
|
| 5 |
+
## How It Works
|
| 6 |
+
|
| 7 |
+
The `app.py` server acts as a lightweight wrapper around `main.py`:
|
| 8 |
+
- **No Model Loading**: The server doesn't load models, parsers, or RAG components
|
| 9 |
+
- **Subprocess Calls**: Uses `subprocess` to call `main.py` with command line arguments
|
| 10 |
+
- **Localhost Only**: Runs only on 127.0.0.1 for security
|
| 11 |
+
- **Simple Interface**: Provides basic GET/POST endpoints
|
| 12 |
+
|
| 13 |
+
## API Endpoints
|
| 14 |
+
|
| 15 |
+
### 1. Health Check
|
| 16 |
+
**GET** `http://127.0.0.1:5000/health`
|
| 17 |
+
|
| 18 |
+
Check if the Flask server is running.
|
| 19 |
+
|
| 20 |
+
**Response:**
|
| 21 |
+
```json
|
| 22 |
+
{
|
| 23 |
+
"status": "healthy",
|
| 24 |
+
"message": "Flask server is running"
|
| 25 |
+
}
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### 2. System Status
|
| 29 |
+
**GET** `http://127.0.0.1:5000/hackrx/status`
|
| 30 |
+
|
| 31 |
+
Check if main.py is ready and working.
|
| 32 |
+
|
| 33 |
+
**Response:**
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"status": "ready",
|
| 37 |
+
"message": "main.py is ready"
|
| 38 |
+
}
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### 3. Document Upload
|
| 42 |
+
**POST** `http://127.0.0.1:5000/hackrx/upload`
|
| 43 |
+
|
| 44 |
+
Upload and process a document using main.py.
|
| 45 |
+
|
| 46 |
+
**Form Data:**
|
| 47 |
+
- `file`: The document file to upload
|
| 48 |
+
|
| 49 |
+
**Response:**
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"success": true,
|
| 53 |
+
"message": "Document processed successfully",
|
| 54 |
+
"chunks_processed": 45,
|
| 55 |
+
"processing_time": 2.34,
|
| 56 |
+
"filename": "document.pdf"
|
| 57 |
+
}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### 4. Query Processing
|
| 61 |
+
**POST** `http://127.0.0.1:5000/hackrx/run`
|
| 62 |
+
|
| 63 |
+
Process questions about uploaded documents using main.py.
|
| 64 |
+
|
| 65 |
+
**Request Body:**
|
| 66 |
+
```json
|
| 67 |
+
{
|
| 68 |
+
"questions": [
|
| 69 |
+
"What is covered under this policy?",
|
| 70 |
+
"What is the maximum coverage amount?"
|
| 71 |
+
]
|
| 72 |
+
}
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
**Response:**
|
| 76 |
+
```json
|
| 77 |
+
{
|
| 78 |
+
"answers": [
|
| 79 |
+
{
|
| 80 |
+
"question": "What is covered under this policy?",
|
| 81 |
+
"answer": "Based on the policy document, the following are covered...",
|
| 82 |
+
"decision": "COVERED",
|
| 83 |
+
"confidence": 0.85,
|
| 84 |
+
"processing_time": 1.23
|
| 85 |
+
}
|
| 86 |
+
]
|
| 87 |
+
}
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## How the Server Works
|
| 91 |
+
|
| 92 |
+
### Document Upload Process:
|
| 93 |
+
1. Flask receives uploaded file
|
| 94 |
+
2. Saves file to `uploads/` directory
|
| 95 |
+
3. Calls: `python main.py --upload /path/to/file`
|
| 96 |
+
4. Parses output from main.py
|
| 97 |
+
5. Returns JSON response
|
| 98 |
+
|
| 99 |
+
### Query Processing Process:
|
| 100 |
+
1. Flask receives JSON with questions
|
| 101 |
+
2. For each question:
|
| 102 |
+
- Creates temporary file with question
|
| 103 |
+
- Calls: `python main.py --query /path/to/question.txt`
|
| 104 |
+
- Parses structured output from main.py
|
| 105 |
+
- Extracts decision, confidence, justification
|
| 106 |
+
3. Returns JSON with all answers
|
| 107 |
+
|
| 108 |
+
### Status Check Process:
|
| 109 |
+
1. Calls: `python main.py --status`
|
| 110 |
+
2. Checks if main.py responds successfully
|
| 111 |
+
3. Returns status JSON
|
| 112 |
+
|
| 113 |
+
## Usage Examples
|
| 114 |
+
|
| 115 |
+
### Python Example
|
| 116 |
+
|
| 117 |
+
```python
|
| 118 |
+
import requests
|
| 119 |
+
|
| 120 |
+
BASE_URL = "http://127.0.0.1:5000"
|
| 121 |
+
|
| 122 |
+
# 1. Upload a document
|
| 123 |
+
with open("document.pdf", "rb") as f:
|
| 124 |
+
files = {"file": f}
|
| 125 |
+
response = requests.post(f"{BASE_URL}/hackrx/upload", files=files)
|
| 126 |
+
print("Upload response:", response.json())
|
| 127 |
+
|
| 128 |
+
# 2. Process queries
|
| 129 |
+
questions = [
|
| 130 |
+
"What is covered under this policy?",
|
| 131 |
+
"What is the maximum coverage amount?"
|
| 132 |
+
]
|
| 133 |
+
|
| 134 |
+
payload = {"questions": questions}
|
| 135 |
+
response = requests.post(
|
| 136 |
+
f"{BASE_URL}/hackrx/run",
|
| 137 |
+
json=payload,
|
| 138 |
+
headers={"Content-Type": "application/json"}
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
answers = response.json()["answers"]
|
| 142 |
+
for answer in answers:
|
| 143 |
+
print(f"Q: {answer['question']}")
|
| 144 |
+
print(f"A: {answer['answer']}")
|
| 145 |
+
print(f"Decision: {answer['decision']}")
|
| 146 |
+
print(f"Confidence: {answer['confidence']}")
|
| 147 |
+
print("---")
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### cURL Examples
|
| 151 |
+
|
| 152 |
+
**Health Check:**
|
| 153 |
+
```bash
|
| 154 |
+
curl http://127.0.0.1:5000/health
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
**System Status:**
|
| 158 |
+
```bash
|
| 159 |
+
curl http://127.0.0.1:5000/hackrx/status
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
**Upload Document:**
|
| 163 |
+
```bash
|
| 164 |
+
curl -X POST -F "file=@document.pdf" http://127.0.0.1:5000/hackrx/upload
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
**Process Queries:**
|
| 168 |
+
```bash
|
| 169 |
+
curl -X POST \
|
| 170 |
+
-H "Content-Type: application/json" \
|
| 171 |
+
-d '{"questions": ["What is covered under this policy?"]}' \
|
| 172 |
+
http://127.0.0.1:5000/hackrx/run
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
## Running the Server
|
| 176 |
+
|
| 177 |
+
1. **Start the server:**
|
| 178 |
+
```bash
|
| 179 |
+
python app.py
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
2. **Test the API:**
|
| 183 |
+
```bash
|
| 184 |
+
python test_api.py
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
## Command Line Interface
|
| 188 |
+
|
| 189 |
+
The main.py now supports command line arguments:
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
# Process a single query
|
| 193 |
+
python main.py --query question.txt
|
| 194 |
+
|
| 195 |
+
# Upload and process a document
|
| 196 |
+
python main.py --upload document.pdf
|
| 197 |
+
|
| 198 |
+
# Check system status
|
| 199 |
+
python main.py --status
|
| 200 |
+
|
| 201 |
+
# Interactive mode (default)
|
| 202 |
+
python main.py
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
## Advantages
|
| 206 |
+
|
| 207 |
+
1. **No Duplication**: Doesn't load models or initialize RAG system
|
| 208 |
+
2. **Lightweight**: Minimal memory footprint
|
| 209 |
+
3. **Simple**: Easy to understand and maintain
|
| 210 |
+
4. **Secure**: Localhost only
|
| 211 |
+
5. **Reliable**: Uses existing main.py functionality
|
| 212 |
+
|
| 213 |
+
## Error Handling
|
| 214 |
+
|
| 215 |
+
- **File Not Found**: Returns 400 if file doesn't exist
|
| 216 |
+
- **Unsupported Format**: Returns 400 for unsupported file types
|
| 217 |
+
- **Processing Errors**: Returns 500 with error details
|
| 218 |
+
- **Timeouts**: 60 seconds for queries, 120 seconds for uploads
|
| 219 |
+
|
| 220 |
+
## Notes
|
| 221 |
+
|
| 222 |
+
- **main.py Required**: The server requires main.py to be in the same directory
|
| 223 |
+
- **Python Path**: Assumes `python` command is available
|
| 224 |
+
- **File Cleanup**: Temporary files are automatically cleaned up
|
| 225 |
+
- **Upload Directory**: Creates `uploads/` directory if it doesn't exist
|
README.md
CHANGED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: mit
|
| 3 |
-
---
|
|
|
|
|
|
|
|
|
|
|
|
SIMPLE_API_README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Simple Flask API for RAG System
|
| 2 |
+
|
| 3 |
+
A simplified Flask API server that provides localhost access to the main.py RAG system functionality.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Localhost Only**: Runs only on 127.0.0.1 (localhost)
|
| 8 |
+
- **Simple Authentication**: No authentication required for localhost use
|
| 9 |
+
- **Core Operations**: Document upload and query processing
|
| 10 |
+
- **GET/POST Operations**: Simple REST endpoints
|
| 11 |
+
|
| 12 |
+
## API Endpoints
|
| 13 |
+
|
| 14 |
+
### 1. Health Check
|
| 15 |
+
**GET** `http://127.0.0.1:5000/health`
|
| 16 |
+
|
| 17 |
+
Check if the server is running.
|
| 18 |
+
|
| 19 |
+
**Response:**
|
| 20 |
+
```json
|
| 21 |
+
{
|
| 22 |
+
"status": "healthy",
|
| 23 |
+
"rag_system_initialized": true
|
| 24 |
+
}
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### 2. System Status
|
| 28 |
+
**GET** `http://127.0.0.1:5000/hackrx/status`
|
| 29 |
+
|
| 30 |
+
Check if the RAG system is ready.
|
| 31 |
+
|
| 32 |
+
**Response:**
|
| 33 |
+
```json
|
| 34 |
+
{
|
| 35 |
+
"status": "ready",
|
| 36 |
+
"message": "RAG system is ready"
|
| 37 |
+
}
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 3. Document Upload
|
| 41 |
+
**POST** `http://127.0.0.1:5000/hackrx/upload`
|
| 42 |
+
|
| 43 |
+
Upload and process a document.
|
| 44 |
+
|
| 45 |
+
**Form Data:**
|
| 46 |
+
- `file`: The document file to upload
|
| 47 |
+
|
| 48 |
+
**Supported File Types:**
|
| 49 |
+
- PDF (.pdf)
|
| 50 |
+
- Text (.txt)
|
| 51 |
+
- Word (.docx)
|
| 52 |
+
- HTML (.html, .htm)
|
| 53 |
+
- Email (.eml, .msg)
|
| 54 |
+
- CSV (.csv)
|
| 55 |
+
- JSON (.json)
|
| 56 |
+
|
| 57 |
+
**Response:**
|
| 58 |
+
```json
|
| 59 |
+
{
|
| 60 |
+
"success": true,
|
| 61 |
+
"message": "Document processed successfully",
|
| 62 |
+
"chunks_processed": 45,
|
| 63 |
+
"processing_time": 2.34,
|
| 64 |
+
"filename": "document.pdf"
|
| 65 |
+
}
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### 4. Query Processing
|
| 69 |
+
**POST** `http://127.0.0.1:5000/hackrx/run`
|
| 70 |
+
|
| 71 |
+
Process questions about uploaded documents.
|
| 72 |
+
|
| 73 |
+
**Request Body:**
|
| 74 |
+
```json
|
| 75 |
+
{
|
| 76 |
+
"questions": [
|
| 77 |
+
"What is covered under this policy?",
|
| 78 |
+
"What is the maximum coverage amount?",
|
| 79 |
+
"What documents are required for claims?"
|
| 80 |
+
]
|
| 81 |
+
}
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
**Response:**
|
| 85 |
+
```json
|
| 86 |
+
{
|
| 87 |
+
"answers": [
|
| 88 |
+
{
|
| 89 |
+
"question": "What is covered under this policy?",
|
| 90 |
+
"answer": "Based on the policy document, the following are covered...",
|
| 91 |
+
"decision": "COVERED",
|
| 92 |
+
"confidence": 0.85,
|
| 93 |
+
"processing_time": 1.23
|
| 94 |
+
}
|
| 95 |
+
]
|
| 96 |
+
}
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
## Usage Examples
|
| 100 |
+
|
| 101 |
+
### Python Example
|
| 102 |
+
|
| 103 |
+
```python
|
| 104 |
+
import requests
|
| 105 |
+
import json
|
| 106 |
+
|
| 107 |
+
BASE_URL = "http://127.0.0.1:5000"
|
| 108 |
+
|
| 109 |
+
# 1. Upload a document
|
| 110 |
+
with open("document.pdf", "rb") as f:
|
| 111 |
+
files = {"file": f}
|
| 112 |
+
response = requests.post(f"{BASE_URL}/hackrx/upload", files=files)
|
| 113 |
+
print("Upload response:", response.json())
|
| 114 |
+
|
| 115 |
+
# 2. Process queries
|
| 116 |
+
questions = [
|
| 117 |
+
"What is covered under this policy?",
|
| 118 |
+
"What is the maximum coverage amount?"
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
payload = {"questions": questions}
|
| 122 |
+
response = requests.post(
|
| 123 |
+
f"{BASE_URL}/hackrx/run",
|
| 124 |
+
json=payload,
|
| 125 |
+
headers={"Content-Type": "application/json"}
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
answers = response.json()["answers"]
|
| 129 |
+
for answer in answers:
|
| 130 |
+
print(f"Q: {answer['question']}")
|
| 131 |
+
print(f"A: {answer['answer']}")
|
| 132 |
+
print(f"Decision: {answer['decision']}")
|
| 133 |
+
print(f"Confidence: {answer['confidence']}")
|
| 134 |
+
print("---")
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### cURL Examples
|
| 138 |
+
|
| 139 |
+
**Health Check:**
|
| 140 |
+
```bash
|
| 141 |
+
curl http://127.0.0.1:5000/health
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
**System Status:**
|
| 145 |
+
```bash
|
| 146 |
+
curl http://127.0.0.1:5000/hackrx/status
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
**Upload Document:**
|
| 150 |
+
```bash
|
| 151 |
+
curl -X POST -F "file=@document.pdf" http://127.0.0.1:5000/hackrx/upload
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
**Process Queries:**
|
| 155 |
+
```bash
|
| 156 |
+
curl -X POST \
|
| 157 |
+
-H "Content-Type: application/json" \
|
| 158 |
+
-d '{"questions": ["What is covered under this policy?"]}' \
|
| 159 |
+
http://127.0.0.1:5000/hackrx/run
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
## Running the Server
|
| 163 |
+
|
| 164 |
+
1. **Start the server:**
|
| 165 |
+
```bash
|
| 166 |
+
python app.py
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
2. **Test the API:**
|
| 170 |
+
```bash
|
| 171 |
+
python test_api.py
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
## Error Responses
|
| 175 |
+
|
| 176 |
+
- `200`: Success
|
| 177 |
+
- `400`: Bad Request (missing parameters, invalid data)
|
| 178 |
+
- `500`: Internal Server Error
|
| 179 |
+
|
| 180 |
+
Error response format:
|
| 181 |
+
```json
|
| 182 |
+
{
|
| 183 |
+
"error": "Error description"
|
| 184 |
+
}
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
## Notes
|
| 188 |
+
|
| 189 |
+
- **Localhost Only**: The server only accepts connections from localhost (127.0.0.1)
|
| 190 |
+
- **No Authentication**: No API keys or authentication required for localhost use
|
| 191 |
+
- **Simple Interface**: Focused on core document upload and query processing
|
| 192 |
+
- **Automatic Cleanup**: Uploaded files are automatically cleaned up after processing
|
app.py
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Flask API Pipeline for Advanced RAG System
|
| 3 |
+
Provides REST API endpoints for document processing and query analysis
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from flask import Flask, request, jsonify, send_file
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
import json
|
| 10 |
+
import logging
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import List, Dict, Any, Optional
|
| 14 |
+
import tempfile
|
| 15 |
+
import shutil
|
| 16 |
+
|
| 17 |
+
# Import RAG system components
|
| 18 |
+
from rag_system import AdvancedRAGSystem, QueryResult
|
| 19 |
+
from document_processer import AdvancedDocumentProcessor, DocumentChunk
|
| 20 |
+
from vector_database import VectorDatabase, SearchResult
|
| 21 |
+
from query_parser import AdvancedQueryParser, ParsedQuery
|
| 22 |
+
from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
|
| 23 |
+
|
| 24 |
+
# Configure logging
|
| 25 |
+
logging.basicConfig(
|
| 26 |
+
level=logging.INFO,
|
| 27 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 28 |
+
handlers=[
|
| 29 |
+
logging.FileHandler('app.log'),
|
| 30 |
+
logging.StreamHandler()
|
| 31 |
+
]
|
| 32 |
+
)
|
| 33 |
+
logger = logging.getLogger(__name__)
|
| 34 |
+
|
| 35 |
+
app = Flask(__name__)
|
| 36 |
+
|
| 37 |
+
# Global RAG system instance
|
| 38 |
+
rag_system = None
|
| 39 |
+
system_initialized = False
|
| 40 |
+
|
| 41 |
+
# Request logging middleware
|
| 42 |
+
@app.before_request
|
| 43 |
+
def log_request_info():
|
| 44 |
+
"""Log all incoming requests"""
|
| 45 |
+
logger.info(f"Request: {request.method} {request.url}")
|
| 46 |
+
if request.method == 'POST':
|
| 47 |
+
logger.info(f"Request data: {request.get_data()[:200]}...") # Log first 200 chars
|
| 48 |
+
|
| 49 |
+
@app.after_request
|
| 50 |
+
def log_response_info(response):
|
| 51 |
+
"""Log all outgoing responses"""
|
| 52 |
+
logger.info(f"Response: {response.status_code} for {request.method} {request.url}")
|
| 53 |
+
return response
|
| 54 |
+
|
| 55 |
+
@app.route('/', methods=['GET'])
|
| 56 |
+
def root():
|
| 57 |
+
"""Root endpoint with API documentation"""
|
| 58 |
+
logger.info("Root endpoint accessed")
|
| 59 |
+
return jsonify({
|
| 60 |
+
'message': 'Advanced RAG System API',
|
| 61 |
+
'version': '1.0.0',
|
| 62 |
+
'status': 'running',
|
| 63 |
+
'endpoints': {
|
| 64 |
+
'health': 'GET /api/health',
|
| 65 |
+
'status': 'GET /api/status',
|
| 66 |
+
'upload': 'POST /api/upload',
|
| 67 |
+
'query': 'POST /api/query',
|
| 68 |
+
'batch_query': 'POST /api/batch_query',
|
| 69 |
+
'validate': 'GET /api/validate',
|
| 70 |
+
'audit': 'GET /api/audit',
|
| 71 |
+
'statistics': 'GET /api/statistics',
|
| 72 |
+
'export': 'POST /api/export',
|
| 73 |
+
'clear': 'POST /api/clear',
|
| 74 |
+
'keep_alive': 'GET /api/keep-alive'
|
| 75 |
+
},
|
| 76 |
+
'description': 'Insurance Policy Analysis RAG System',
|
| 77 |
+
'features': [
|
| 78 |
+
'Document upload and processing',
|
| 79 |
+
'Natural language query processing',
|
| 80 |
+
'Policy coverage analysis',
|
| 81 |
+
'Claim requirement extraction',
|
| 82 |
+
'Audit trail and statistics'
|
| 83 |
+
],
|
| 84 |
+
'timestamp': datetime.now().isoformat()
|
| 85 |
+
})
|
| 86 |
+
|
| 87 |
+
def initialize_rag_system():
|
| 88 |
+
"""Initialize the RAG system"""
|
| 89 |
+
global rag_system, system_initialized
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
logger.info("Initializing RAG system...")
|
| 93 |
+
rag_system = AdvancedRAGSystem(
|
| 94 |
+
model_path="./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
|
| 95 |
+
use_gpu=False, # Use CPU for better compatibility
|
| 96 |
+
vector_db_path="./vector_db"
|
| 97 |
+
)
|
| 98 |
+
system_initialized = True
|
| 99 |
+
logger.info("RAG system initialized successfully!")
|
| 100 |
+
return True
|
| 101 |
+
except Exception as e:
|
| 102 |
+
logger.error(f"Failed to initialize RAG system: {e}")
|
| 103 |
+
system_initialized = False
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
def ensure_system_ready():
|
| 107 |
+
"""Ensure the RAG system is ready"""
|
| 108 |
+
if not system_initialized or rag_system is None:
|
| 109 |
+
if not initialize_rag_system():
|
| 110 |
+
return False
|
| 111 |
+
return True
|
| 112 |
+
|
| 113 |
+
@app.route('/api/health', methods=['GET'])
|
| 114 |
+
def health_check():
|
| 115 |
+
"""Health check endpoint"""
|
| 116 |
+
logger.info("Health check requested")
|
| 117 |
+
try:
|
| 118 |
+
response = {
|
| 119 |
+
'status': 'healthy',
|
| 120 |
+
'timestamp': datetime.now().isoformat(),
|
| 121 |
+
'system_initialized': system_initialized,
|
| 122 |
+
'rag_system_ready': rag_system is not None
|
| 123 |
+
}
|
| 124 |
+
logger.info(f"Health check response: {response}")
|
| 125 |
+
return jsonify(response)
|
| 126 |
+
except Exception as e:
|
| 127 |
+
logger.error(f"Health check failed: {e}")
|
| 128 |
+
return jsonify({'error': str(e)}), 500
|
| 129 |
+
|
| 130 |
+
@app.route('/api/keep-alive', methods=['GET'])
|
| 131 |
+
def keep_alive():
|
| 132 |
+
"""Keep-alive endpoint to prevent auto-termination"""
|
| 133 |
+
logger.info("Keep-alive ping received")
|
| 134 |
+
return jsonify({
|
| 135 |
+
'status': 'alive',
|
| 136 |
+
'timestamp': datetime.now().isoformat(),
|
| 137 |
+
'message': 'Server is running'
|
| 138 |
+
})
|
| 139 |
+
|
| 140 |
+
@app.route('/hackrx/run', methods=['POST'])
|
| 141 |
+
def hackrx_run():
|
| 142 |
+
"""Main endpoint for hackathon - processes queries with document URL"""
|
| 143 |
+
logger.info("HackRX run endpoint accessed")
|
| 144 |
+
|
| 145 |
+
# Check Authorization header
|
| 146 |
+
auth_header = request.headers.get('Authorization')
|
| 147 |
+
if not auth_header or not auth_header.startswith('Bearer '):
|
| 148 |
+
logger.warning("Missing or invalid Authorization header")
|
| 149 |
+
return jsonify({'error': 'Unauthorized'}), 401
|
| 150 |
+
|
| 151 |
+
api_key = auth_header.split(' ')[1]
|
| 152 |
+
# For now, accept any Bearer token (you can add validation later)
|
| 153 |
+
logger.info(f"API key provided: {api_key[:10]}...")
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
data = request.get_json()
|
| 157 |
+
if not data:
|
| 158 |
+
logger.warning("No JSON data provided in hackrx/run request")
|
| 159 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 160 |
+
|
| 161 |
+
# Extract documents URL and questions
|
| 162 |
+
documents_url = data.get('documents')
|
| 163 |
+
questions = data.get('questions')
|
| 164 |
+
|
| 165 |
+
if not questions or not isinstance(questions, list):
|
| 166 |
+
logger.warning("No questions list provided in hackrx/run request")
|
| 167 |
+
return jsonify({'error': 'No questions list provided'}), 400
|
| 168 |
+
|
| 169 |
+
logger.info(f"Processing {len(questions)} questions")
|
| 170 |
+
if documents_url:
|
| 171 |
+
logger.info(f"Document URL provided: {documents_url}")
|
| 172 |
+
|
| 173 |
+
# Ensure system is ready
|
| 174 |
+
if not ensure_system_ready():
|
| 175 |
+
logger.error("RAG system not ready for hackrx/run")
|
| 176 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 177 |
+
|
| 178 |
+
# If document URL is provided, download and process it
|
| 179 |
+
if documents_url:
|
| 180 |
+
try:
|
| 181 |
+
logger.info("Downloading document from URL...")
|
| 182 |
+
import requests
|
| 183 |
+
response = requests.get(documents_url, timeout=30)
|
| 184 |
+
if response.status_code == 200:
|
| 185 |
+
# Save document temporarily
|
| 186 |
+
temp_file = f"temp_document_{int(time.time())}.pdf"
|
| 187 |
+
with open(temp_file, 'wb') as f:
|
| 188 |
+
f.write(response.content)
|
| 189 |
+
|
| 190 |
+
# Process document
|
| 191 |
+
logger.info("Processing downloaded document...")
|
| 192 |
+
chunks = rag_system.ingest_document(temp_file, use_ocr=False)
|
| 193 |
+
logger.info(f"Document processed: {len(chunks)} chunks created")
|
| 194 |
+
|
| 195 |
+
# Clean up
|
| 196 |
+
os.remove(temp_file)
|
| 197 |
+
else:
|
| 198 |
+
logger.warning(f"Failed to download document: {response.status_code}")
|
| 199 |
+
except Exception as e:
|
| 200 |
+
logger.error(f"Error downloading/processing document: {e}")
|
| 201 |
+
|
| 202 |
+
# Process questions
|
| 203 |
+
answers = []
|
| 204 |
+
total_start_time = time.time()
|
| 205 |
+
|
| 206 |
+
for i, question in enumerate(questions):
|
| 207 |
+
if not isinstance(question, str) or not question.strip():
|
| 208 |
+
continue
|
| 209 |
+
|
| 210 |
+
logger.info(f"Processing question {i+1}/{len(questions)}: {question}")
|
| 211 |
+
start_time = time.time()
|
| 212 |
+
|
| 213 |
+
try:
|
| 214 |
+
result = rag_system.process_query(question)
|
| 215 |
+
processing_time = time.time() - start_time
|
| 216 |
+
|
| 217 |
+
# Extract just the answer text for hackathon format
|
| 218 |
+
answer_text = result.reasoning_result.justification
|
| 219 |
+
|
| 220 |
+
answers.append(answer_text)
|
| 221 |
+
logger.info(f"Question {i+1} processed successfully in {processing_time:.2f}s")
|
| 222 |
+
|
| 223 |
+
except Exception as e:
|
| 224 |
+
logger.error(f"Error processing question {i+1}: {e}")
|
| 225 |
+
answers.append(f"Error processing query: {str(e)}")
|
| 226 |
+
|
| 227 |
+
total_time = time.time() - total_start_time
|
| 228 |
+
logger.info(f"HackRX run completed: {len(answers)} answers in {total_time:.2f}s")
|
| 229 |
+
|
| 230 |
+
# Return in hackathon format
|
| 231 |
+
return jsonify({'answers': answers})
|
| 232 |
+
|
| 233 |
+
except Exception as e:
|
| 234 |
+
logger.error(f"HackRX run failed: {e}")
|
| 235 |
+
return jsonify({'error': f'Processing failed: {str(e)}'}), 500
|
| 236 |
+
|
| 237 |
+
@app.route('/hackrx/upload', methods=['POST'])
|
| 238 |
+
def hackrx_upload():
|
| 239 |
+
"""Upload endpoint for hackathon"""
|
| 240 |
+
logger.info("HackRX upload endpoint accessed")
|
| 241 |
+
try:
|
| 242 |
+
# Check if file is uploaded
|
| 243 |
+
if 'file' not in request.files:
|
| 244 |
+
logger.warning("No file provided in hackrx/upload request")
|
| 245 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 246 |
+
|
| 247 |
+
file = request.files['file']
|
| 248 |
+
if file.filename == '':
|
| 249 |
+
logger.warning("Empty filename in hackrx/upload request")
|
| 250 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 251 |
+
|
| 252 |
+
logger.info(f"Processing file: {file.filename}")
|
| 253 |
+
|
| 254 |
+
# Ensure system is ready
|
| 255 |
+
if not ensure_system_ready():
|
| 256 |
+
logger.error("RAG system not ready for upload")
|
| 257 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 258 |
+
|
| 259 |
+
# Check file type
|
| 260 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 261 |
+
file_extension = Path(file.filename).suffix.lower()
|
| 262 |
+
|
| 263 |
+
if file_extension not in supported_extensions:
|
| 264 |
+
logger.warning(f"Unsupported file type: {file_extension}")
|
| 265 |
+
return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
|
| 266 |
+
|
| 267 |
+
# Save uploaded file
|
| 268 |
+
upload_dir = Path('uploads')
|
| 269 |
+
upload_dir.mkdir(exist_ok=True)
|
| 270 |
+
|
| 271 |
+
file_path = upload_dir / file.filename
|
| 272 |
+
file.save(str(file_path))
|
| 273 |
+
logger.info(f"File saved to: {file_path}")
|
| 274 |
+
|
| 275 |
+
try:
|
| 276 |
+
# Process document
|
| 277 |
+
start_time = time.time()
|
| 278 |
+
logger.info("Starting document ingestion...")
|
| 279 |
+
chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
|
| 280 |
+
processing_time = time.time() - start_time
|
| 281 |
+
|
| 282 |
+
logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
|
| 283 |
+
|
| 284 |
+
# Clean up uploaded file
|
| 285 |
+
os.remove(str(file_path))
|
| 286 |
+
logger.info("Temporary file cleaned up")
|
| 287 |
+
|
| 288 |
+
response = {
|
| 289 |
+
'success': True,
|
| 290 |
+
'message': 'Document processed successfully',
|
| 291 |
+
'filename': file.filename,
|
| 292 |
+
'chunks_processed': len(chunks),
|
| 293 |
+
'processing_time': processing_time,
|
| 294 |
+
'file_type': file_extension,
|
| 295 |
+
'timestamp': datetime.now().isoformat()
|
| 296 |
+
}
|
| 297 |
+
logger.info(f"HackRX upload response: {response}")
|
| 298 |
+
return jsonify(response)
|
| 299 |
+
|
| 300 |
+
except Exception as e:
|
| 301 |
+
# Clean up on error
|
| 302 |
+
if os.path.exists(str(file_path)):
|
| 303 |
+
os.remove(str(file_path))
|
| 304 |
+
logger.info("Cleaned up file after error")
|
| 305 |
+
logger.error(f"Document processing error: {e}")
|
| 306 |
+
raise e
|
| 307 |
+
|
| 308 |
+
except Exception as e:
|
| 309 |
+
logger.error(f"HackRX upload failed: {e}")
|
| 310 |
+
return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
|
| 311 |
+
|
| 312 |
+
@app.route('/api/status', methods=['GET'])
|
| 313 |
+
def system_status():
|
| 314 |
+
"""Get detailed system status"""
|
| 315 |
+
try:
|
| 316 |
+
if not ensure_system_ready():
|
| 317 |
+
return jsonify({
|
| 318 |
+
'status': 'error',
|
| 319 |
+
'message': 'RAG system initialization failed'
|
| 320 |
+
}), 500
|
| 321 |
+
|
| 322 |
+
# Get system statistics
|
| 323 |
+
stats = rag_system.get_system_statistics()
|
| 324 |
+
|
| 325 |
+
return jsonify({
|
| 326 |
+
'status': 'ready',
|
| 327 |
+
'system_statistics': stats,
|
| 328 |
+
'timestamp': datetime.now().isoformat()
|
| 329 |
+
})
|
| 330 |
+
|
| 331 |
+
except Exception as e:
|
| 332 |
+
logger.error(f"Status check failed: {e}")
|
| 333 |
+
return jsonify({
|
| 334 |
+
'status': 'error',
|
| 335 |
+
'message': f'Status check failed: {str(e)}'
|
| 336 |
+
}), 500
|
| 337 |
+
|
| 338 |
+
@app.route('/api/upload', methods=['POST'])
|
| 339 |
+
def upload_document():
|
| 340 |
+
"""Upload and process a document"""
|
| 341 |
+
logger.info("Document upload requested")
|
| 342 |
+
try:
|
| 343 |
+
# Check if file is uploaded
|
| 344 |
+
if 'file' not in request.files:
|
| 345 |
+
logger.warning("No file provided in upload request")
|
| 346 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 347 |
+
|
| 348 |
+
file = request.files['file']
|
| 349 |
+
if file.filename == '':
|
| 350 |
+
logger.warning("Empty filename in upload request")
|
| 351 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 352 |
+
|
| 353 |
+
logger.info(f"Processing file: {file.filename}")
|
| 354 |
+
|
| 355 |
+
# Ensure system is ready
|
| 356 |
+
if not ensure_system_ready():
|
| 357 |
+
logger.error("RAG system not ready for upload")
|
| 358 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 359 |
+
|
| 360 |
+
# Check file type
|
| 361 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 362 |
+
file_extension = Path(file.filename).suffix.lower()
|
| 363 |
+
|
| 364 |
+
if file_extension not in supported_extensions:
|
| 365 |
+
logger.warning(f"Unsupported file type: {file_extension}")
|
| 366 |
+
return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
|
| 367 |
+
|
| 368 |
+
# Get OCR option
|
| 369 |
+
use_ocr = request.form.get('use_ocr', 'false').lower() == 'true'
|
| 370 |
+
logger.info(f"OCR enabled: {use_ocr}")
|
| 371 |
+
|
| 372 |
+
# Save uploaded file
|
| 373 |
+
upload_dir = Path('uploads')
|
| 374 |
+
upload_dir.mkdir(exist_ok=True)
|
| 375 |
+
|
| 376 |
+
file_path = upload_dir / file.filename
|
| 377 |
+
file.save(str(file_path))
|
| 378 |
+
logger.info(f"File saved to: {file_path}")
|
| 379 |
+
|
| 380 |
+
try:
|
| 381 |
+
# Process document
|
| 382 |
+
start_time = time.time()
|
| 383 |
+
logger.info("Starting document ingestion...")
|
| 384 |
+
chunks = rag_system.ingest_document(str(file_path), use_ocr=use_ocr)
|
| 385 |
+
processing_time = time.time() - start_time
|
| 386 |
+
|
| 387 |
+
logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
|
| 388 |
+
|
| 389 |
+
# Clean up uploaded file
|
| 390 |
+
os.remove(str(file_path))
|
| 391 |
+
logger.info("Temporary file cleaned up")
|
| 392 |
+
|
| 393 |
+
response = {
|
| 394 |
+
'success': True,
|
| 395 |
+
'message': 'Document processed successfully',
|
| 396 |
+
'filename': file.filename,
|
| 397 |
+
'chunks_processed': len(chunks),
|
| 398 |
+
'processing_time': processing_time,
|
| 399 |
+
'file_type': file_extension,
|
| 400 |
+
'ocr_used': use_ocr,
|
| 401 |
+
'timestamp': datetime.now().isoformat()
|
| 402 |
+
}
|
| 403 |
+
logger.info(f"Upload response: {response}")
|
| 404 |
+
return jsonify(response)
|
| 405 |
+
|
| 406 |
+
except Exception as e:
|
| 407 |
+
# Clean up on error
|
| 408 |
+
if os.path.exists(str(file_path)):
|
| 409 |
+
os.remove(str(file_path))
|
| 410 |
+
logger.info("Cleaned up file after error")
|
| 411 |
+
logger.error(f"Document processing error: {e}")
|
| 412 |
+
raise e
|
| 413 |
+
|
| 414 |
+
except Exception as e:
|
| 415 |
+
logger.error(f"Document upload failed: {e}")
|
| 416 |
+
return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
|
| 417 |
+
|
| 418 |
+
@app.route('/api/query', methods=['POST'])
|
| 419 |
+
def process_query():
|
| 420 |
+
"""Process a single query"""
|
| 421 |
+
logger.info("Single query processing requested")
|
| 422 |
+
try:
|
| 423 |
+
data = request.get_json()
|
| 424 |
+
if not data:
|
| 425 |
+
logger.warning("No JSON data provided in query request")
|
| 426 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 427 |
+
|
| 428 |
+
query = data.get('query')
|
| 429 |
+
if not query or not isinstance(query, str):
|
| 430 |
+
logger.warning("Invalid query provided")
|
| 431 |
+
return jsonify({'error': 'Invalid query provided'}), 400
|
| 432 |
+
|
| 433 |
+
logger.info(f"Processing query: {query}")
|
| 434 |
+
|
| 435 |
+
# Ensure system is ready
|
| 436 |
+
if not ensure_system_ready():
|
| 437 |
+
logger.error("RAG system not ready for query processing")
|
| 438 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 439 |
+
|
| 440 |
+
# Process query
|
| 441 |
+
start_time = time.time()
|
| 442 |
+
logger.info("Starting query processing...")
|
| 443 |
+
result = rag_system.process_query(query)
|
| 444 |
+
processing_time = time.time() - start_time
|
| 445 |
+
|
| 446 |
+
logger.info(f"Query processed successfully in {processing_time:.2f}s")
|
| 447 |
+
|
| 448 |
+
# Format response
|
| 449 |
+
response = {
|
| 450 |
+
'query': query,
|
| 451 |
+
'answer': result.reasoning_result.justification,
|
| 452 |
+
'decision': result.reasoning_result.decision,
|
| 453 |
+
'confidence': result.reasoning_result.confidence_score,
|
| 454 |
+
'processing_time': processing_time,
|
| 455 |
+
'timestamp': datetime.now().isoformat(),
|
| 456 |
+
'metadata': {
|
| 457 |
+
'amount': result.reasoning_result.amount,
|
| 458 |
+
'waiting_period': result.reasoning_result.waiting_period,
|
| 459 |
+
'relevant_clauses': result.reasoning_result.relevant_clauses,
|
| 460 |
+
'conditions': result.reasoning_result.conditions,
|
| 461 |
+
'exclusions': result.reasoning_result.exclusions,
|
| 462 |
+
'required_documents': result.reasoning_result.required_documents
|
| 463 |
+
}
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
logger.info(f"Query response: {response}")
|
| 467 |
+
return jsonify(response)
|
| 468 |
+
|
| 469 |
+
except Exception as e:
|
| 470 |
+
logger.error(f"Query processing failed: {e}")
|
| 471 |
+
return jsonify({'error': f'Query processing failed: {str(e)}'}), 500
|
| 472 |
+
|
| 473 |
+
@app.route('/api/batch_query', methods=['POST'])
|
| 474 |
+
def process_batch_queries():
|
| 475 |
+
"""Process multiple queries"""
|
| 476 |
+
try:
|
| 477 |
+
data = request.get_json()
|
| 478 |
+
if not data:
|
| 479 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 480 |
+
|
| 481 |
+
queries = data.get('queries')
|
| 482 |
+
if not queries or not isinstance(queries, list):
|
| 483 |
+
return jsonify({'error': 'Invalid queries list provided'}), 400
|
| 484 |
+
|
| 485 |
+
# Ensure system is ready
|
| 486 |
+
if not ensure_system_ready():
|
| 487 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 488 |
+
|
| 489 |
+
results = []
|
| 490 |
+
total_start_time = time.time()
|
| 491 |
+
|
| 492 |
+
for query in queries:
|
| 493 |
+
if not isinstance(query, str) or not query.strip():
|
| 494 |
+
continue
|
| 495 |
+
|
| 496 |
+
try:
|
| 497 |
+
start_time = time.time()
|
| 498 |
+
result = rag_system.process_query(query)
|
| 499 |
+
processing_time = time.time() - start_time
|
| 500 |
+
|
| 501 |
+
query_result = {
|
| 502 |
+
'query': query,
|
| 503 |
+
'answer': result.reasoning_result.justification,
|
| 504 |
+
'decision': result.reasoning_result.decision,
|
| 505 |
+
'confidence': result.reasoning_result.confidence_score,
|
| 506 |
+
'processing_time': processing_time,
|
| 507 |
+
'metadata': {
|
| 508 |
+
'amount': result.reasoning_result.amount,
|
| 509 |
+
'waiting_period': result.reasoning_result.waiting_period,
|
| 510 |
+
'relevant_clauses': result.reasoning_result.relevant_clauses,
|
| 511 |
+
'conditions': result.reasoning_result.conditions,
|
| 512 |
+
'exclusions': result.reasoning_result.exclusions,
|
| 513 |
+
'required_documents': result.reasoning_result.required_documents
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
results.append(query_result)
|
| 518 |
+
|
| 519 |
+
except Exception as e:
|
| 520 |
+
results.append({
|
| 521 |
+
'query': query,
|
| 522 |
+
'answer': f"Error processing query: {str(e)}",
|
| 523 |
+
'decision': 'ERROR',
|
| 524 |
+
'confidence': 0.0,
|
| 525 |
+
'processing_time': 0.0,
|
| 526 |
+
'metadata': {}
|
| 527 |
+
})
|
| 528 |
+
|
| 529 |
+
total_time = time.time() - total_start_time
|
| 530 |
+
|
| 531 |
+
return jsonify({
|
| 532 |
+
'results': results,
|
| 533 |
+
'total_queries': len(queries),
|
| 534 |
+
'successful_queries': len([r for r in results if r['decision'] != 'ERROR']),
|
| 535 |
+
'total_processing_time': total_time,
|
| 536 |
+
'timestamp': datetime.now().isoformat()
|
| 537 |
+
})
|
| 538 |
+
|
| 539 |
+
except Exception as e:
|
| 540 |
+
logger.error(f"Batch query processing failed: {e}")
|
| 541 |
+
return jsonify({'error': f'Batch query processing failed: {str(e)}'}), 500
|
| 542 |
+
|
| 543 |
+
@app.route('/api/validate', methods=['GET'])
|
| 544 |
+
def validate_system():
|
| 545 |
+
"""Validate system components"""
|
| 546 |
+
try:
|
| 547 |
+
if not ensure_system_ready():
|
| 548 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 549 |
+
|
| 550 |
+
validation = rag_system.validate_system()
|
| 551 |
+
return jsonify(validation)
|
| 552 |
+
|
| 553 |
+
except Exception as e:
|
| 554 |
+
logger.error(f"System validation failed: {e}")
|
| 555 |
+
return jsonify({'error': f'System validation failed: {str(e)}'}), 500
|
| 556 |
+
|
| 557 |
+
@app.route('/api/audit', methods=['GET'])
|
| 558 |
+
def get_audit_trail():
|
| 559 |
+
"""Get audit trail"""
|
| 560 |
+
try:
|
| 561 |
+
if not ensure_system_ready():
|
| 562 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 563 |
+
|
| 564 |
+
audit_log = rag_system.get_audit_trail()
|
| 565 |
+
return jsonify({
|
| 566 |
+
'audit_trail': audit_log,
|
| 567 |
+
'total_entries': len(audit_log),
|
| 568 |
+
'timestamp': datetime.now().isoformat()
|
| 569 |
+
})
|
| 570 |
+
|
| 571 |
+
except Exception as e:
|
| 572 |
+
logger.error(f"Audit trail retrieval failed: {e}")
|
| 573 |
+
return jsonify({'error': f'Audit trail retrieval failed: {str(e)}'}), 500
|
| 574 |
+
|
| 575 |
+
@app.route('/api/export', methods=['POST'])
|
| 576 |
+
def export_system_data():
|
| 577 |
+
"""Export system data"""
|
| 578 |
+
try:
|
| 579 |
+
data = request.get_json() or {}
|
| 580 |
+
filename = data.get('filename', f'system_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json')
|
| 581 |
+
|
| 582 |
+
if not ensure_system_ready():
|
| 583 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 584 |
+
|
| 585 |
+
success = rag_system.export_system_data(filename)
|
| 586 |
+
|
| 587 |
+
if success:
|
| 588 |
+
return jsonify({
|
| 589 |
+
'success': True,
|
| 590 |
+
'message': 'System data exported successfully',
|
| 591 |
+
'filename': filename,
|
| 592 |
+
'timestamp': datetime.now().isoformat()
|
| 593 |
+
})
|
| 594 |
+
else:
|
| 595 |
+
return jsonify({'error': 'Failed to export system data'}), 500
|
| 596 |
+
|
| 597 |
+
except Exception as e:
|
| 598 |
+
logger.error(f"System export failed: {e}")
|
| 599 |
+
return jsonify({'error': f'System export failed: {str(e)}'}), 500
|
| 600 |
+
|
| 601 |
+
@app.route('/api/clear', methods=['POST'])
|
| 602 |
+
def clear_system():
|
| 603 |
+
"""Clear system data"""
|
| 604 |
+
try:
|
| 605 |
+
if not ensure_system_ready():
|
| 606 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 607 |
+
|
| 608 |
+
success = rag_system.clear_system()
|
| 609 |
+
|
| 610 |
+
return jsonify({
|
| 611 |
+
'success': success,
|
| 612 |
+
'message': 'System cleared successfully' if success else 'Failed to clear system',
|
| 613 |
+
'timestamp': datetime.now().isoformat()
|
| 614 |
+
})
|
| 615 |
+
|
| 616 |
+
except Exception as e:
|
| 617 |
+
logger.error(f"System clear failed: {e}")
|
| 618 |
+
return jsonify({'error': f'System clear failed: {str(e)}'}), 500
|
| 619 |
+
|
| 620 |
+
@app.route('/api/statistics', methods=['GET'])
|
| 621 |
+
def get_statistics():
|
| 622 |
+
"""Get system statistics"""
|
| 623 |
+
try:
|
| 624 |
+
if not ensure_system_ready():
|
| 625 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 626 |
+
|
| 627 |
+
stats = rag_system.get_system_statistics()
|
| 628 |
+
return jsonify(stats)
|
| 629 |
+
|
| 630 |
+
except Exception as e:
|
| 631 |
+
logger.error(f"Statistics retrieval failed: {e}")
|
| 632 |
+
return jsonify({'error': f'Statistics retrieval failed: {str(e)}'}), 500
|
| 633 |
+
|
| 634 |
+
# Error handlers
|
| 635 |
+
@app.errorhandler(404)
|
| 636 |
+
def not_found(error):
|
| 637 |
+
return jsonify({'error': 'Endpoint not found'}), 404
|
| 638 |
+
|
| 639 |
+
@app.errorhandler(500)
|
| 640 |
+
def internal_error(error):
|
| 641 |
+
return jsonify({'error': 'Internal server error'}), 500
|
| 642 |
+
|
| 643 |
+
@app.errorhandler(Exception)
|
| 644 |
+
def handle_exception(e):
|
| 645 |
+
logger.error(f"Unhandled exception: {e}")
|
| 646 |
+
return jsonify({'error': 'Internal server error'}), 500
|
| 647 |
+
|
| 648 |
+
if __name__ == '__main__':
|
| 649 |
+
print("🚀 Starting Flask API Pipeline for Advanced RAG System")
|
| 650 |
+
print("=" * 60)
|
| 651 |
+
print("📋 Available endpoints:")
|
| 652 |
+
print(" GET /api/health - Health check")
|
| 653 |
+
print(" GET /api/status - System status")
|
| 654 |
+
print(" POST /api/upload - Upload document")
|
| 655 |
+
print(" POST /api/query - Process single query")
|
| 656 |
+
print(" POST /api/batch_query - Process multiple queries")
|
| 657 |
+
print(" GET /api/validate - Validate system")
|
| 658 |
+
print(" GET /api/audit - Get audit trail")
|
| 659 |
+
print(" POST /api/export - Export system data")
|
| 660 |
+
print(" POST /api/clear - Clear system")
|
| 661 |
+
print(" GET /api/statistics - Get statistics")
|
| 662 |
+
print("=" * 60)
|
| 663 |
+
|
| 664 |
+
# Initialize system on startup
|
| 665 |
+
logger.info("Starting Flask API Pipeline")
|
| 666 |
+
if initialize_rag_system():
|
| 667 |
+
print("✅ RAG system initialized successfully!")
|
| 668 |
+
logger.info("RAG system initialized successfully")
|
| 669 |
+
else:
|
| 670 |
+
print("⚠️ RAG system initialization failed - will retry on first request")
|
| 671 |
+
logger.warning("RAG system initialization failed")
|
| 672 |
+
|
| 673 |
+
print(f"🌐 Server will run on http://127.0.0.1:5000")
|
| 674 |
+
print("=" * 60)
|
| 675 |
+
logger.info("Starting Flask server...")
|
| 676 |
+
|
| 677 |
+
try:
|
| 678 |
+
app.run(
|
| 679 |
+
debug=False,
|
| 680 |
+
host='0.0.0.0',
|
| 681 |
+
port=5000,
|
| 682 |
+
threaded=True,
|
| 683 |
+
use_reloader=False # Prevent auto-restart issues
|
| 684 |
+
)
|
| 685 |
+
except KeyboardInterrupt:
|
| 686 |
+
logger.info("Server stopped by user (Ctrl+C)")
|
| 687 |
+
print("\n👋 Server stopped by user")
|
| 688 |
+
except Exception as e:
|
| 689 |
+
logger.error(f"Server crashed: {e}")
|
| 690 |
+
print(f"❌ Server crashed: {e}")
|
| 691 |
+
raise
|
app2.py
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Flask API Pipeline for RAG System
|
| 3 |
+
Dedicated API server for the Advanced RAG System
|
| 4 |
+
Separate from main.py (command-line pipeline)
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from flask import Flask, request, jsonify, send_file
|
| 8 |
+
import os
|
| 9 |
+
import time
|
| 10 |
+
import json
|
| 11 |
+
import logging
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from typing import List, Dict, Any, Optional
|
| 15 |
+
import tempfile
|
| 16 |
+
import shutil
|
| 17 |
+
import requests
|
| 18 |
+
|
| 19 |
+
# Import RAG system components
|
| 20 |
+
from rag_system import AdvancedRAGSystem, QueryResult
|
| 21 |
+
from document_processer import AdvancedDocumentProcessor, DocumentChunk
|
| 22 |
+
from vector_database import VectorDatabase, SearchResult
|
| 23 |
+
from query_parser import AdvancedQueryParser, ParsedQuery
|
| 24 |
+
from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
|
| 25 |
+
|
| 26 |
+
# Configure logging
|
| 27 |
+
logging.basicConfig(
|
| 28 |
+
level=logging.INFO,
|
| 29 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 30 |
+
handlers=[
|
| 31 |
+
logging.FileHandler('app2.log'),
|
| 32 |
+
logging.StreamHandler()
|
| 33 |
+
]
|
| 34 |
+
)
|
| 35 |
+
logger = logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
app = Flask(__name__)
|
| 38 |
+
|
| 39 |
+
# Global RAG system instance
|
| 40 |
+
rag_system = None
|
| 41 |
+
system_initialized = False
|
| 42 |
+
|
| 43 |
+
# Request logging middleware
|
| 44 |
+
@app.before_request
|
| 45 |
+
def log_request_info():
|
| 46 |
+
"""Log all incoming requests"""
|
| 47 |
+
logger.info(f"Request: {request.method} {request.url}")
|
| 48 |
+
if request.method == 'POST':
|
| 49 |
+
logger.info(f"Request data: {request.get_data()[:200]}...")
|
| 50 |
+
|
| 51 |
+
@app.after_request
|
| 52 |
+
def log_response_info(response):
|
| 53 |
+
"""Log all outgoing responses"""
|
| 54 |
+
logger.info(f"Response: {response.status_code} for {request.method} {request.url}")
|
| 55 |
+
return response
|
| 56 |
+
|
| 57 |
+
@app.route('/', methods=['GET'])
|
| 58 |
+
def root():
|
| 59 |
+
"""Root endpoint with API documentation"""
|
| 60 |
+
logger.info("Root endpoint accessed")
|
| 61 |
+
return jsonify({
|
| 62 |
+
'message': 'RAG System API Pipeline',
|
| 63 |
+
'version': '2.0.0',
|
| 64 |
+
'status': 'running',
|
| 65 |
+
'endpoints': {
|
| 66 |
+
'health': 'GET /api/health',
|
| 67 |
+
'status': 'GET /api/status',
|
| 68 |
+
'upload': 'POST /api/upload',
|
| 69 |
+
'query': 'POST /api/query',
|
| 70 |
+
'batch_query': 'POST /api/batch_query',
|
| 71 |
+
'validate': 'GET /api/validate',
|
| 72 |
+
'audit': 'GET /api/audit',
|
| 73 |
+
'statistics': 'GET /api/statistics',
|
| 74 |
+
'export': 'POST /api/export',
|
| 75 |
+
'clear': 'POST /api/clear',
|
| 76 |
+
'keep_alive': 'GET /api/keep-alive',
|
| 77 |
+
'hackrx_run': 'POST /hackrx/run',
|
| 78 |
+
'hackrx_upload': 'POST /hackrx/upload'
|
| 79 |
+
},
|
| 80 |
+
'description': 'Advanced RAG System API Pipeline',
|
| 81 |
+
'features': [
|
| 82 |
+
'Document upload and processing',
|
| 83 |
+
'Natural language query processing',
|
| 84 |
+
'Policy coverage analysis',
|
| 85 |
+
'Claim requirement extraction',
|
| 86 |
+
'Audit trail and statistics',
|
| 87 |
+
'Hackathon-compatible endpoints'
|
| 88 |
+
],
|
| 89 |
+
'timestamp': datetime.now().isoformat()
|
| 90 |
+
})
|
| 91 |
+
|
| 92 |
+
def initialize_rag_system():
|
| 93 |
+
"""Initialize the RAG system"""
|
| 94 |
+
global rag_system, system_initialized
|
| 95 |
+
|
| 96 |
+
try:
|
| 97 |
+
logger.info("Initializing RAG system...")
|
| 98 |
+
rag_system = AdvancedRAGSystem(
|
| 99 |
+
model_path="./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
|
| 100 |
+
use_gpu=False, # Use CPU for better compatibility
|
| 101 |
+
vector_db_path="./vector_db"
|
| 102 |
+
)
|
| 103 |
+
system_initialized = True
|
| 104 |
+
logger.info("RAG system initialized successfully!")
|
| 105 |
+
return True
|
| 106 |
+
except Exception as e:
|
| 107 |
+
logger.error(f"RAG system initialization failed: {e}")
|
| 108 |
+
return False
|
| 109 |
+
|
| 110 |
+
def ensure_system_ready():
|
| 111 |
+
"""Ensure RAG system is ready"""
|
| 112 |
+
if not system_initialized:
|
| 113 |
+
return initialize_rag_system()
|
| 114 |
+
return True
|
| 115 |
+
|
| 116 |
+
@app.route('/api/health', methods=['GET'])
|
| 117 |
+
def health_check():
|
| 118 |
+
"""Health check endpoint"""
|
| 119 |
+
logger.info("Health check requested")
|
| 120 |
+
try:
|
| 121 |
+
return jsonify({
|
| 122 |
+
'status': 'healthy',
|
| 123 |
+
'timestamp': datetime.now().isoformat(),
|
| 124 |
+
'system_initialized': system_initialized,
|
| 125 |
+
'rag_system_ready': system_initialized,
|
| 126 |
+
'message': 'RAG System API is running'
|
| 127 |
+
})
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.error(f"Health check failed: {e}")
|
| 130 |
+
return jsonify({'error': f'Health check failed: {str(e)}'}), 500
|
| 131 |
+
|
| 132 |
+
@app.route('/api/keep-alive', methods=['GET'])
|
| 133 |
+
def keep_alive():
|
| 134 |
+
"""Keep-alive endpoint to prevent server termination"""
|
| 135 |
+
logger.info("Keep-alive ping received")
|
| 136 |
+
return jsonify({
|
| 137 |
+
'status': 'alive',
|
| 138 |
+
'timestamp': datetime.now().isoformat(),
|
| 139 |
+
'message': 'RAG System API is running'
|
| 140 |
+
})
|
| 141 |
+
|
| 142 |
+
@app.route('/hackrx/run', methods=['POST'])
|
| 143 |
+
def hackrx_run():
|
| 144 |
+
"""Hackathon run endpoint"""
|
| 145 |
+
logger.info("HackRX run endpoint accessed")
|
| 146 |
+
try:
|
| 147 |
+
# Check authorization
|
| 148 |
+
auth_header = request.headers.get('Authorization')
|
| 149 |
+
if not auth_header or not auth_header.startswith('Bearer '):
|
| 150 |
+
logger.warning("Missing or invalid Authorization header")
|
| 151 |
+
return jsonify({'error': 'Unauthorized'}), 401
|
| 152 |
+
|
| 153 |
+
api_key = auth_header.split(' ')[1]
|
| 154 |
+
# For now, accept any Bearer token
|
| 155 |
+
logger.info(f"API key provided: {api_key[:10]}...")
|
| 156 |
+
|
| 157 |
+
# Get request data
|
| 158 |
+
data = request.get_json()
|
| 159 |
+
if not data:
|
| 160 |
+
logger.warning("No JSON data provided")
|
| 161 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 162 |
+
|
| 163 |
+
documents_url = data.get('documents')
|
| 164 |
+
questions = data.get('questions', [])
|
| 165 |
+
|
| 166 |
+
if not questions:
|
| 167 |
+
logger.warning("No questions provided")
|
| 168 |
+
return jsonify({'error': 'No questions provided'}), 400
|
| 169 |
+
|
| 170 |
+
logger.info(f"Processing {len(questions)} questions")
|
| 171 |
+
if documents_url:
|
| 172 |
+
logger.info(f"Document URL provided: {documents_url}")
|
| 173 |
+
|
| 174 |
+
# Ensure system is ready
|
| 175 |
+
if not ensure_system_ready():
|
| 176 |
+
logger.error("RAG system not ready")
|
| 177 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 178 |
+
|
| 179 |
+
# Download and process document if URL provided
|
| 180 |
+
if documents_url:
|
| 181 |
+
try:
|
| 182 |
+
logger.info("Downloading document from URL...")
|
| 183 |
+
response = requests.get(documents_url, timeout=30)
|
| 184 |
+
response.raise_for_status()
|
| 185 |
+
|
| 186 |
+
# Save to temporary file
|
| 187 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
|
| 188 |
+
temp_file.write(response.content)
|
| 189 |
+
temp_file.close()
|
| 190 |
+
|
| 191 |
+
logger.info(f"Document downloaded to: {temp_file.name}")
|
| 192 |
+
|
| 193 |
+
# Ingest document
|
| 194 |
+
chunks = rag_system.ingest_document(temp_file.name, use_ocr=False)
|
| 195 |
+
logger.info(f"Document ingested: {len(chunks)} chunks created")
|
| 196 |
+
|
| 197 |
+
# Clean up
|
| 198 |
+
os.unlink(temp_file.name)
|
| 199 |
+
|
| 200 |
+
except Exception as e:
|
| 201 |
+
logger.error(f"Document download/processing failed: {e}")
|
| 202 |
+
return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
|
| 203 |
+
|
| 204 |
+
# Process questions
|
| 205 |
+
answers = []
|
| 206 |
+
for i, question in enumerate(questions):
|
| 207 |
+
try:
|
| 208 |
+
logger.info(f"Processing question {i+1}/{len(questions)}: {question[:50]}...")
|
| 209 |
+
result = rag_system.process_query(question)
|
| 210 |
+
|
| 211 |
+
if result and result.reasoning_result:
|
| 212 |
+
answer = result.reasoning_result.justification
|
| 213 |
+
answers.append(answer)
|
| 214 |
+
logger.info(f"Question {i+1} processed successfully")
|
| 215 |
+
else:
|
| 216 |
+
answers.append("Unable to process this question.")
|
| 217 |
+
logger.warning(f"Question {i+1} failed to process")
|
| 218 |
+
|
| 219 |
+
except Exception as e:
|
| 220 |
+
logger.error(f"Question {i+1} processing failed: {e}")
|
| 221 |
+
answers.append(f"Error processing question: {str(e)}")
|
| 222 |
+
|
| 223 |
+
response_data = {'answers': answers}
|
| 224 |
+
logger.info(f"HackRX run completed: {len(answers)} answers generated")
|
| 225 |
+
return jsonify(response_data)
|
| 226 |
+
|
| 227 |
+
except Exception as e:
|
| 228 |
+
logger.error(f"HackRX run failed: {e}")
|
| 229 |
+
return jsonify({'error': f'Processing failed: {str(e)}'}), 500
|
| 230 |
+
|
| 231 |
+
@app.route('/hackrx/upload', methods=['POST'])
|
| 232 |
+
def hackrx_upload():
|
| 233 |
+
"""Hackathon upload endpoint"""
|
| 234 |
+
logger.info("HackRX upload endpoint accessed")
|
| 235 |
+
try:
|
| 236 |
+
# Check if file is uploaded
|
| 237 |
+
if 'file' not in request.files:
|
| 238 |
+
logger.warning("No file provided in hackrx/upload request")
|
| 239 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 240 |
+
|
| 241 |
+
file = request.files['file']
|
| 242 |
+
if file.filename == '':
|
| 243 |
+
logger.warning("Empty filename in hackrx/upload request")
|
| 244 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 245 |
+
|
| 246 |
+
logger.info(f"Processing file: {file.filename}")
|
| 247 |
+
|
| 248 |
+
# Ensure system is ready
|
| 249 |
+
if not ensure_system_ready():
|
| 250 |
+
logger.error("RAG system not ready for upload")
|
| 251 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 252 |
+
|
| 253 |
+
# Check file type
|
| 254 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 255 |
+
file_extension = Path(file.filename).suffix.lower()
|
| 256 |
+
|
| 257 |
+
if file_extension not in supported_extensions:
|
| 258 |
+
logger.warning(f"Unsupported file type: {file_extension}")
|
| 259 |
+
return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
|
| 260 |
+
|
| 261 |
+
# Save uploaded file
|
| 262 |
+
upload_dir = Path('uploads')
|
| 263 |
+
upload_dir.mkdir(exist_ok=True)
|
| 264 |
+
|
| 265 |
+
file_path = upload_dir / file.filename
|
| 266 |
+
file.save(str(file_path))
|
| 267 |
+
logger.info(f"File saved to: {file_path}")
|
| 268 |
+
|
| 269 |
+
try:
|
| 270 |
+
# Process document
|
| 271 |
+
start_time = time.time()
|
| 272 |
+
logger.info("Starting document ingestion...")
|
| 273 |
+
chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
|
| 274 |
+
processing_time = time.time() - start_time
|
| 275 |
+
|
| 276 |
+
logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
|
| 277 |
+
|
| 278 |
+
# Clean up uploaded file
|
| 279 |
+
os.remove(str(file_path))
|
| 280 |
+
logger.info("Temporary file cleaned up")
|
| 281 |
+
|
| 282 |
+
response = {
|
| 283 |
+
'success': True,
|
| 284 |
+
'message': 'Document processed successfully',
|
| 285 |
+
'filename': file.filename,
|
| 286 |
+
'chunks_processed': len(chunks),
|
| 287 |
+
'processing_time': processing_time,
|
| 288 |
+
'file_type': file_extension,
|
| 289 |
+
'timestamp': datetime.now().isoformat()
|
| 290 |
+
}
|
| 291 |
+
logger.info(f"HackRX upload response: {response}")
|
| 292 |
+
return jsonify(response)
|
| 293 |
+
|
| 294 |
+
except Exception as e:
|
| 295 |
+
# Clean up on error
|
| 296 |
+
if os.path.exists(str(file_path)):
|
| 297 |
+
os.remove(str(file_path))
|
| 298 |
+
logger.info("Cleaned up file after error")
|
| 299 |
+
logger.error(f"Document processing error: {e}")
|
| 300 |
+
raise e
|
| 301 |
+
|
| 302 |
+
except Exception as e:
|
| 303 |
+
logger.error(f"HackRX upload failed: {e}")
|
| 304 |
+
return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
|
| 305 |
+
|
| 306 |
+
@app.route('/api/status', methods=['GET'])
|
| 307 |
+
def system_status():
|
| 308 |
+
"""Get detailed system status"""
|
| 309 |
+
try:
|
| 310 |
+
if not ensure_system_ready():
|
| 311 |
+
return jsonify({
|
| 312 |
+
'status': 'error',
|
| 313 |
+
'message': 'RAG system initialization failed'
|
| 314 |
+
}), 500
|
| 315 |
+
|
| 316 |
+
# Get system statistics
|
| 317 |
+
stats = rag_system.get_system_statistics()
|
| 318 |
+
|
| 319 |
+
return jsonify({
|
| 320 |
+
'status': 'ready',
|
| 321 |
+
'system_statistics': stats,
|
| 322 |
+
'timestamp': datetime.now().isoformat()
|
| 323 |
+
})
|
| 324 |
+
|
| 325 |
+
except Exception as e:
|
| 326 |
+
logger.error(f"Status check failed: {e}")
|
| 327 |
+
return jsonify({
|
| 328 |
+
'status': 'error',
|
| 329 |
+
'message': f'Status check failed: {str(e)}'
|
| 330 |
+
}), 500
|
| 331 |
+
|
| 332 |
+
@app.route('/api/upload', methods=['POST'])
|
| 333 |
+
def upload_document():
|
| 334 |
+
"""Upload document endpoint"""
|
| 335 |
+
logger.info("Upload endpoint accessed")
|
| 336 |
+
try:
|
| 337 |
+
# Check if file is uploaded
|
| 338 |
+
if 'file' not in request.files:
|
| 339 |
+
logger.warning("No file provided in upload request")
|
| 340 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 341 |
+
|
| 342 |
+
file = request.files['file']
|
| 343 |
+
if file.filename == '':
|
| 344 |
+
logger.warning("Empty filename in upload request")
|
| 345 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 346 |
+
|
| 347 |
+
logger.info(f"Processing file: {file.filename}")
|
| 348 |
+
|
| 349 |
+
# Ensure system is ready
|
| 350 |
+
if not ensure_system_ready():
|
| 351 |
+
logger.error("RAG system not ready for upload")
|
| 352 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 353 |
+
|
| 354 |
+
# Check file type
|
| 355 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 356 |
+
file_extension = Path(file.filename).suffix.lower()
|
| 357 |
+
|
| 358 |
+
if file_extension not in supported_extensions:
|
| 359 |
+
logger.warning(f"Unsupported file type: {file_extension}")
|
| 360 |
+
return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
|
| 361 |
+
|
| 362 |
+
# Save uploaded file
|
| 363 |
+
upload_dir = Path('uploads')
|
| 364 |
+
upload_dir.mkdir(exist_ok=True)
|
| 365 |
+
|
| 366 |
+
file_path = upload_dir / file.filename
|
| 367 |
+
file.save(str(file_path))
|
| 368 |
+
logger.info(f"File saved to: {file_path}")
|
| 369 |
+
|
| 370 |
+
try:
|
| 371 |
+
# Process document
|
| 372 |
+
start_time = time.time()
|
| 373 |
+
logger.info("Starting document ingestion...")
|
| 374 |
+
chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
|
| 375 |
+
processing_time = time.time() - start_time
|
| 376 |
+
|
| 377 |
+
logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
|
| 378 |
+
|
| 379 |
+
# Clean up uploaded file
|
| 380 |
+
os.remove(str(file_path))
|
| 381 |
+
logger.info("Temporary file cleaned up")
|
| 382 |
+
|
| 383 |
+
response = {
|
| 384 |
+
'success': True,
|
| 385 |
+
'message': 'Document uploaded and processed successfully',
|
| 386 |
+
'filename': file.filename,
|
| 387 |
+
'chunks_processed': len(chunks),
|
| 388 |
+
'processing_time': processing_time,
|
| 389 |
+
'file_type': file_extension,
|
| 390 |
+
'timestamp': datetime.now().isoformat()
|
| 391 |
+
}
|
| 392 |
+
logger.info(f"Upload response: {response}")
|
| 393 |
+
return jsonify(response)
|
| 394 |
+
|
| 395 |
+
except Exception as e:
|
| 396 |
+
# Clean up on error
|
| 397 |
+
if os.path.exists(str(file_path)):
|
| 398 |
+
os.remove(str(file_path))
|
| 399 |
+
logger.info("Cleaned up file after error")
|
| 400 |
+
logger.error(f"Document processing error: {e}")
|
| 401 |
+
raise e
|
| 402 |
+
|
| 403 |
+
except Exception as e:
|
| 404 |
+
logger.error(f"Upload failed: {e}")
|
| 405 |
+
return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
|
| 406 |
+
|
| 407 |
+
@app.route('/api/query', methods=['POST'])
|
| 408 |
+
def process_query():
|
| 409 |
+
"""Process single query endpoint"""
|
| 410 |
+
logger.info("Query endpoint accessed")
|
| 411 |
+
try:
|
| 412 |
+
data = request.get_json()
|
| 413 |
+
if not data:
|
| 414 |
+
logger.warning("No JSON data provided")
|
| 415 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 416 |
+
|
| 417 |
+
query = data.get('query')
|
| 418 |
+
if not query:
|
| 419 |
+
logger.warning("No query provided")
|
| 420 |
+
return jsonify({'error': 'No query provided'}), 400
|
| 421 |
+
|
| 422 |
+
logger.info(f"Processing query: {query[:50]}...")
|
| 423 |
+
|
| 424 |
+
# Ensure system is ready
|
| 425 |
+
if not ensure_system_ready():
|
| 426 |
+
logger.error("RAG system not ready")
|
| 427 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 428 |
+
|
| 429 |
+
# Process query
|
| 430 |
+
start_time = time.time()
|
| 431 |
+
result = rag_system.process_query(query)
|
| 432 |
+
processing_time = time.time() - start_time
|
| 433 |
+
|
| 434 |
+
if result:
|
| 435 |
+
response = {
|
| 436 |
+
'success': True,
|
| 437 |
+
'query': query,
|
| 438 |
+
'answer': result.reasoning_result.justification if result.reasoning_result else "No answer generated",
|
| 439 |
+
'confidence': result.reasoning_result.confidence if result.reasoning_result else 0.0,
|
| 440 |
+
'processing_time': processing_time,
|
| 441 |
+
'timestamp': datetime.now().isoformat()
|
| 442 |
+
}
|
| 443 |
+
logger.info(f"Query processed successfully in {processing_time:.2f}s")
|
| 444 |
+
return jsonify(response)
|
| 445 |
+
else:
|
| 446 |
+
logger.warning("Query processing returned no result")
|
| 447 |
+
return jsonify({'error': 'No result generated'}), 500
|
| 448 |
+
|
| 449 |
+
except Exception as e:
|
| 450 |
+
logger.error(f"Query processing failed: {e}")
|
| 451 |
+
return jsonify({'error': f'Query processing failed: {str(e)}'}), 500
|
| 452 |
+
|
| 453 |
+
@app.route('/api/batch_query', methods=['POST'])
|
| 454 |
+
def process_batch_queries():
|
| 455 |
+
"""Process multiple queries endpoint"""
|
| 456 |
+
logger.info("Batch query endpoint accessed")
|
| 457 |
+
try:
|
| 458 |
+
data = request.get_json()
|
| 459 |
+
if not data:
|
| 460 |
+
logger.warning("No JSON data provided")
|
| 461 |
+
return jsonify({'error': 'No JSON data provided'}), 400
|
| 462 |
+
|
| 463 |
+
queries = data.get('queries', [])
|
| 464 |
+
if not queries:
|
| 465 |
+
logger.warning("No queries provided")
|
| 466 |
+
return jsonify({'error': 'No queries provided'}), 400
|
| 467 |
+
|
| 468 |
+
logger.info(f"Processing {len(queries)} queries")
|
| 469 |
+
|
| 470 |
+
# Ensure system is ready
|
| 471 |
+
if not ensure_system_ready():
|
| 472 |
+
logger.error("RAG system not ready")
|
| 473 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 474 |
+
|
| 475 |
+
# Process queries
|
| 476 |
+
results = []
|
| 477 |
+
start_time = time.time()
|
| 478 |
+
|
| 479 |
+
for i, query in enumerate(queries):
|
| 480 |
+
try:
|
| 481 |
+
logger.info(f"Processing query {i+1}/{len(queries)}: {query[:50]}...")
|
| 482 |
+
result = rag_system.process_query(query)
|
| 483 |
+
|
| 484 |
+
if result and result.reasoning_result:
|
| 485 |
+
results.append({
|
| 486 |
+
'query': query,
|
| 487 |
+
'answer': result.reasoning_result.justification,
|
| 488 |
+
'confidence': result.reasoning_result.confidence,
|
| 489 |
+
'success': True
|
| 490 |
+
})
|
| 491 |
+
else:
|
| 492 |
+
results.append({
|
| 493 |
+
'query': query,
|
| 494 |
+
'answer': "No answer generated",
|
| 495 |
+
'confidence': 0.0,
|
| 496 |
+
'success': False
|
| 497 |
+
})
|
| 498 |
+
|
| 499 |
+
except Exception as e:
|
| 500 |
+
logger.error(f"Query {i+1} processing failed: {e}")
|
| 501 |
+
results.append({
|
| 502 |
+
'query': query,
|
| 503 |
+
'answer': f"Error: {str(e)}",
|
| 504 |
+
'confidence': 0.0,
|
| 505 |
+
'success': False
|
| 506 |
+
})
|
| 507 |
+
|
| 508 |
+
processing_time = time.time() - start_time
|
| 509 |
+
|
| 510 |
+
response = {
|
| 511 |
+
'success': True,
|
| 512 |
+
'queries_processed': len(queries),
|
| 513 |
+
'successful_queries': len([r for r in results if r['success']]),
|
| 514 |
+
'processing_time': processing_time,
|
| 515 |
+
'results': results,
|
| 516 |
+
'timestamp': datetime.now().isoformat()
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
logger.info(f"Batch query completed: {len(results)} results in {processing_time:.2f}s")
|
| 520 |
+
return jsonify(response)
|
| 521 |
+
|
| 522 |
+
except Exception as e:
|
| 523 |
+
logger.error(f"Batch query processing failed: {e}")
|
| 524 |
+
return jsonify({'error': f'Batch query processing failed: {str(e)}'}), 500
|
| 525 |
+
|
| 526 |
+
@app.route('/api/validate', methods=['GET'])
|
| 527 |
+
def validate_system():
|
| 528 |
+
"""Validate system endpoint"""
|
| 529 |
+
logger.info("Validate endpoint accessed")
|
| 530 |
+
try:
|
| 531 |
+
if not ensure_system_ready():
|
| 532 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 533 |
+
|
| 534 |
+
# Perform validation checks
|
| 535 |
+
validation_results = {
|
| 536 |
+
'rag_system_ready': system_initialized,
|
| 537 |
+
'vector_db_accessible': True, # Add actual check if needed
|
| 538 |
+
'model_loaded': True, # Add actual check if needed
|
| 539 |
+
'timestamp': datetime.now().isoformat()
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
return jsonify(validation_results)
|
| 543 |
+
|
| 544 |
+
except Exception as e:
|
| 545 |
+
logger.error(f"Validation failed: {e}")
|
| 546 |
+
return jsonify({'error': f'Validation failed: {str(e)}'}), 500
|
| 547 |
+
|
| 548 |
+
@app.route('/api/audit', methods=['GET'])
|
| 549 |
+
def get_audit_trail():
|
| 550 |
+
"""Get audit trail endpoint"""
|
| 551 |
+
logger.info("Audit endpoint accessed")
|
| 552 |
+
try:
|
| 553 |
+
if not ensure_system_ready():
|
| 554 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 555 |
+
|
| 556 |
+
# Get audit trail from RAG system
|
| 557 |
+
audit_trail = rag_system.get_audit_trail()
|
| 558 |
+
|
| 559 |
+
return jsonify({
|
| 560 |
+
'audit_trail': audit_trail,
|
| 561 |
+
'timestamp': datetime.now().isoformat()
|
| 562 |
+
})
|
| 563 |
+
|
| 564 |
+
except Exception as e:
|
| 565 |
+
logger.error(f"Audit trail retrieval failed: {e}")
|
| 566 |
+
return jsonify({'error': f'Audit trail retrieval failed: {str(e)}'}), 500
|
| 567 |
+
|
| 568 |
+
@app.route('/api/export', methods=['POST'])
|
| 569 |
+
def export_system_data():
|
| 570 |
+
"""Export system data endpoint"""
|
| 571 |
+
logger.info("Export endpoint accessed")
|
| 572 |
+
try:
|
| 573 |
+
if not ensure_system_ready():
|
| 574 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 575 |
+
|
| 576 |
+
data = request.get_json() or {}
|
| 577 |
+
export_format = data.get('format', 'json')
|
| 578 |
+
|
| 579 |
+
# Export system data
|
| 580 |
+
export_data = rag_system.export_system_data(export_format)
|
| 581 |
+
|
| 582 |
+
if export_data:
|
| 583 |
+
return jsonify({
|
| 584 |
+
'success': True,
|
| 585 |
+
'format': export_format,
|
| 586 |
+
'data': export_data,
|
| 587 |
+
'timestamp': datetime.now().isoformat()
|
| 588 |
+
})
|
| 589 |
+
else:
|
| 590 |
+
return jsonify({'error': 'Failed to export system data'}), 500
|
| 591 |
+
|
| 592 |
+
except Exception as e:
|
| 593 |
+
logger.error(f"System export failed: {e}")
|
| 594 |
+
return jsonify({'error': f'System export failed: {str(e)}'}), 500
|
| 595 |
+
|
| 596 |
+
@app.route('/api/clear', methods=['POST'])
|
| 597 |
+
def clear_system():
|
| 598 |
+
"""Clear system data endpoint"""
|
| 599 |
+
logger.info("Clear endpoint accessed")
|
| 600 |
+
try:
|
| 601 |
+
if not ensure_system_ready():
|
| 602 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 603 |
+
|
| 604 |
+
success = rag_system.clear_system()
|
| 605 |
+
|
| 606 |
+
return jsonify({
|
| 607 |
+
'success': success,
|
| 608 |
+
'message': 'System cleared successfully' if success else 'Failed to clear system',
|
| 609 |
+
'timestamp': datetime.now().isoformat()
|
| 610 |
+
})
|
| 611 |
+
|
| 612 |
+
except Exception as e:
|
| 613 |
+
logger.error(f"System clear failed: {e}")
|
| 614 |
+
return jsonify({'error': f'System clear failed: {str(e)}'}), 500
|
| 615 |
+
|
| 616 |
+
@app.route('/api/statistics', methods=['GET'])
|
| 617 |
+
def get_statistics():
|
| 618 |
+
"""Get system statistics endpoint"""
|
| 619 |
+
logger.info("Statistics endpoint accessed")
|
| 620 |
+
try:
|
| 621 |
+
if not ensure_system_ready():
|
| 622 |
+
return jsonify({'error': 'RAG system not ready'}), 500
|
| 623 |
+
|
| 624 |
+
stats = rag_system.get_system_statistics()
|
| 625 |
+
return jsonify(stats)
|
| 626 |
+
|
| 627 |
+
except Exception as e:
|
| 628 |
+
logger.error(f"Statistics retrieval failed: {e}")
|
| 629 |
+
return jsonify({'error': f'Statistics retrieval failed: {str(e)}'}), 500
|
| 630 |
+
|
| 631 |
+
# Error handlers
|
| 632 |
+
@app.errorhandler(404)
|
| 633 |
+
def not_found(error):
|
| 634 |
+
return jsonify({'error': 'Endpoint not found'}), 404
|
| 635 |
+
|
| 636 |
+
@app.errorhandler(500)
|
| 637 |
+
def internal_error(error):
|
| 638 |
+
return jsonify({'error': 'Internal server error'}), 500
|
| 639 |
+
|
| 640 |
+
@app.errorhandler(Exception)
|
| 641 |
+
def handle_exception(e):
|
| 642 |
+
logger.error(f"Unhandled exception: {e}")
|
| 643 |
+
return jsonify({'error': 'Internal server error'}), 500
|
| 644 |
+
|
| 645 |
+
if __name__ == '__main__':
|
| 646 |
+
print("🚀 Starting RAG System API Pipeline (app2.py)")
|
| 647 |
+
print("=" * 60)
|
| 648 |
+
print("📋 Available endpoints:")
|
| 649 |
+
print(" GET /api/health - Health check")
|
| 650 |
+
print(" GET /api/status - System status")
|
| 651 |
+
print(" POST /api/upload - Upload document")
|
| 652 |
+
print(" POST /api/query - Process single query")
|
| 653 |
+
print(" POST /api/batch_query - Process multiple queries")
|
| 654 |
+
print(" GET /api/validate - Validate system")
|
| 655 |
+
print(" GET /api/audit - Get audit trail")
|
| 656 |
+
print(" POST /api/export - Export system data")
|
| 657 |
+
print(" POST /api/clear - Clear system")
|
| 658 |
+
print(" GET /api/statistics - Get statistics")
|
| 659 |
+
print(" POST /hackrx/run - Hackathon run endpoint")
|
| 660 |
+
print(" POST /hackrx/upload - Hackathon upload endpoint")
|
| 661 |
+
print("=" * 60)
|
| 662 |
+
|
| 663 |
+
# Initialize system on startup
|
| 664 |
+
logger.info("Starting RAG System API Pipeline")
|
| 665 |
+
if initialize_rag_system():
|
| 666 |
+
print("✅ RAG system initialized successfully!")
|
| 667 |
+
logger.info("RAG system initialized successfully")
|
| 668 |
+
else:
|
| 669 |
+
print("⚠️ RAG system initialization failed - will retry on first request")
|
| 670 |
+
logger.warning("RAG system initialization failed")
|
| 671 |
+
|
| 672 |
+
print(f"🌐 Server will run on http://127.0.0.1:5000")
|
| 673 |
+
print("=" * 60)
|
| 674 |
+
logger.info("Starting Flask server...")
|
| 675 |
+
|
| 676 |
+
try:
|
| 677 |
+
app.run(
|
| 678 |
+
debug=False,
|
| 679 |
+
host='0.0.0.0',
|
| 680 |
+
port=5000,
|
| 681 |
+
threaded=True,
|
| 682 |
+
use_reloader=False # Prevent auto-restart issues
|
| 683 |
+
)
|
| 684 |
+
except KeyboardInterrupt:
|
| 685 |
+
logger.info("Server stopped by user (Ctrl+C)")
|
| 686 |
+
print("\n👋 Server stopped by user")
|
| 687 |
+
except Exception as e:
|
| 688 |
+
logger.error(f"Server crashed: {e}")
|
| 689 |
+
print(f"❌ Server crashed: {e}")
|
| 690 |
+
raise
|
demo.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Comprehensive RAG System Demo
|
| 3 |
+
Demonstrates all features of the RAG system including document ingestion, query processing, and audit trail
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from rag_system_gpu import RAGSystem
|
| 11 |
+
|
| 12 |
+
def print_section(title):
|
| 13 |
+
"""Print a formatted section header"""
|
| 14 |
+
print("\n" + "="*60)
|
| 15 |
+
print(f" {title}")
|
| 16 |
+
print("="*60)
|
| 17 |
+
|
| 18 |
+
def print_subsection(title):
|
| 19 |
+
"""Print a formatted subsection header"""
|
| 20 |
+
print(f"\n--- {title} ---")
|
| 21 |
+
|
| 22 |
+
def demo_document_ingestion():
|
| 23 |
+
"""Interactive document ingestion with file upload from anywhere"""
|
| 24 |
+
print_section("DOCUMENT INGESTION")
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
rag_system = RAGSystem(use_gpu=True) # Use GPU for better performance
|
| 28 |
+
print("✅ GPU-optimized RAG system initialized successfully")
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"❌ Failed to initialize RAG system: {e}")
|
| 31 |
+
print("💡 This might be due to missing model file or dependencies")
|
| 32 |
+
return None
|
| 33 |
+
|
| 34 |
+
print_subsection("PDF Document Upload")
|
| 35 |
+
print("💡 Please provide the path to your PDF document from anywhere on your system.")
|
| 36 |
+
print("💡 Supported formats: PDF files")
|
| 37 |
+
print("💡 Type 'sample' to use the default sample.pdf (if available)")
|
| 38 |
+
print("💡 Type 'browse' to open file browser (if available)")
|
| 39 |
+
print("💡 Type 'quit' to exit")
|
| 40 |
+
print()
|
| 41 |
+
|
| 42 |
+
while True:
|
| 43 |
+
try:
|
| 44 |
+
# Get user input for file path
|
| 45 |
+
file_path = input("📁 Enter PDF file path (or 'browse'/'sample'): ").strip()
|
| 46 |
+
|
| 47 |
+
# Check for exit commands
|
| 48 |
+
if file_path.lower() in ['quit', 'exit', 'q']:
|
| 49 |
+
print("👋 Goodbye!")
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
# Check for browse command
|
| 53 |
+
if file_path.lower() == 'browse':
|
| 54 |
+
try:
|
| 55 |
+
import tkinter as tk
|
| 56 |
+
from tkinter import filedialog
|
| 57 |
+
|
| 58 |
+
# Create a hidden root window
|
| 59 |
+
root = tk.Tk()
|
| 60 |
+
root.withdraw() # Hide the main window
|
| 61 |
+
|
| 62 |
+
# Open file dialog
|
| 63 |
+
file_path = filedialog.askopenfilename(
|
| 64 |
+
title="Select PDF File",
|
| 65 |
+
filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
root.destroy() # Close the hidden window
|
| 69 |
+
|
| 70 |
+
if not file_path:
|
| 71 |
+
print("❌ No file selected.")
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
+
print(f"✅ Selected file: {file_path}")
|
| 75 |
+
|
| 76 |
+
except ImportError:
|
| 77 |
+
print("❌ File browser not available. Please enter the file path manually.")
|
| 78 |
+
continue
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"❌ Error opening file browser: {e}")
|
| 81 |
+
print("💡 Please enter the file path manually.")
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
# Check for sample command
|
| 85 |
+
elif file_path.lower() == 'sample':
|
| 86 |
+
if os.path.exists("sample.pdf"):
|
| 87 |
+
file_path = "sample.pdf"
|
| 88 |
+
print("📄 Using sample.pdf...")
|
| 89 |
+
else:
|
| 90 |
+
print("❌ sample.pdf not found. Please provide a different file path.")
|
| 91 |
+
continue
|
| 92 |
+
|
| 93 |
+
# Skip empty input
|
| 94 |
+
elif not file_path:
|
| 95 |
+
print("⚠️ Please enter a file path.")
|
| 96 |
+
continue
|
| 97 |
+
|
| 98 |
+
# Check if file exists
|
| 99 |
+
if not os.path.exists(file_path):
|
| 100 |
+
print(f"❌ File not found: {file_path}")
|
| 101 |
+
print("💡 Please check the file path and try again.")
|
| 102 |
+
continue
|
| 103 |
+
|
| 104 |
+
# Check if it's a PDF file
|
| 105 |
+
if not file_path.lower().endswith('.pdf'):
|
| 106 |
+
print("❌ Only PDF files are supported.")
|
| 107 |
+
print("💡 Please provide a PDF file.")
|
| 108 |
+
continue
|
| 109 |
+
|
| 110 |
+
print_subsection(f"Processing PDF Document")
|
| 111 |
+
print(f"📄 File: {os.path.basename(file_path)}")
|
| 112 |
+
print(f"📂 Path: {file_path}")
|
| 113 |
+
|
| 114 |
+
# Ask about OCR
|
| 115 |
+
use_ocr_input = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
|
| 116 |
+
use_ocr = use_ocr_input in ['y', 'yes']
|
| 117 |
+
|
| 118 |
+
if use_ocr:
|
| 119 |
+
print("🔍 OCR enabled - processing scanned document...")
|
| 120 |
+
else:
|
| 121 |
+
print("📄 Processing as text-based PDF...")
|
| 122 |
+
|
| 123 |
+
# Ingest the document
|
| 124 |
+
print("⏳ Processing document...")
|
| 125 |
+
start_time = time.time()
|
| 126 |
+
chunks = rag_system.ingest_document(file_path, use_ocr=use_ocr)
|
| 127 |
+
processing_time = time.time() - start_time
|
| 128 |
+
|
| 129 |
+
print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
|
| 130 |
+
print(f"📊 Document chunks created:")
|
| 131 |
+
|
| 132 |
+
for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
|
| 133 |
+
print(f" Chunk {i+1}: {chunk.chunk_id}")
|
| 134 |
+
print(f" Content preview: {chunk.content[:100]}...")
|
| 135 |
+
print()
|
| 136 |
+
|
| 137 |
+
if len(chunks) > 5:
|
| 138 |
+
print(f" ... and {len(chunks) - 5} more chunks")
|
| 139 |
+
|
| 140 |
+
print("🎉 Document processing completed successfully!")
|
| 141 |
+
return rag_system
|
| 142 |
+
|
| 143 |
+
except KeyboardInterrupt:
|
| 144 |
+
print("\n👋 Interrupted by user. Goodbye!")
|
| 145 |
+
return None
|
| 146 |
+
except Exception as e:
|
| 147 |
+
print(f"❌ Error during document ingestion: {e}")
|
| 148 |
+
print("💡 Please check if the file is a valid PDF and try again.")
|
| 149 |
+
print("💡 For scanned documents, try enabling OCR.")
|
| 150 |
+
continue
|
| 151 |
+
|
| 152 |
+
print_subsection(f"Processing PDF Document")
|
| 153 |
+
print(f"📄 File: {selected_file.name}")
|
| 154 |
+
|
| 155 |
+
# Ask about OCR
|
| 156 |
+
use_ocr_input = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
|
| 157 |
+
use_ocr = use_ocr_input in ['y', 'yes']
|
| 158 |
+
|
| 159 |
+
if use_ocr:
|
| 160 |
+
print("🔍 OCR enabled - processing scanned document...")
|
| 161 |
+
else:
|
| 162 |
+
print("📄 Processing as text-based PDF...")
|
| 163 |
+
|
| 164 |
+
# Ingest the document
|
| 165 |
+
print("⏳ Processing document...")
|
| 166 |
+
start_time = time.time()
|
| 167 |
+
chunks = rag_system.ingest_document(str(selected_file), use_ocr=use_ocr)
|
| 168 |
+
processing_time = time.time() - start_time
|
| 169 |
+
|
| 170 |
+
print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
|
| 171 |
+
print(f"📊 Document chunks created:")
|
| 172 |
+
|
| 173 |
+
for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
|
| 174 |
+
print(f" Chunk {i+1}: {chunk.chunk_id}")
|
| 175 |
+
print(f" Content preview: {chunk.content[:100]}...")
|
| 176 |
+
print()
|
| 177 |
+
|
| 178 |
+
if len(chunks) > 5:
|
| 179 |
+
print(f" ... and {len(chunks) - 5} more chunks")
|
| 180 |
+
|
| 181 |
+
print("🎉 Document processing completed successfully!")
|
| 182 |
+
return rag_system
|
| 183 |
+
|
| 184 |
+
except KeyboardInterrupt:
|
| 185 |
+
print("\n👋 Interrupted by user. Goodbye!")
|
| 186 |
+
return None
|
| 187 |
+
except Exception as e:
|
| 188 |
+
print(f"❌ Error during document ingestion: {e}")
|
| 189 |
+
print("💡 Please check if the file is a valid PDF and try again.")
|
| 190 |
+
print("💡 For scanned documents, try enabling OCR.")
|
| 191 |
+
continue
|
| 192 |
+
|
| 193 |
+
def demo_query_processing(rag_system):
|
| 194 |
+
"""Interactive query processing with user input"""
|
| 195 |
+
print_section("INTERACTIVE QUERY PROCESSING")
|
| 196 |
+
|
| 197 |
+
print("💡 Enter your insurance policy questions below.")
|
| 198 |
+
print("💡 Type 'quit' or 'exit' to stop asking questions.")
|
| 199 |
+
print("💡 Type 'help' for example questions.")
|
| 200 |
+
print()
|
| 201 |
+
|
| 202 |
+
results = []
|
| 203 |
+
query_count = 0
|
| 204 |
+
|
| 205 |
+
while True:
|
| 206 |
+
try:
|
| 207 |
+
# Get user input
|
| 208 |
+
user_query = input("🤔 Enter your question: ").strip()
|
| 209 |
+
|
| 210 |
+
# Check for exit commands
|
| 211 |
+
if user_query.lower() in ['quit', 'exit', 'q']:
|
| 212 |
+
print("👋 Goodbye!")
|
| 213 |
+
break
|
| 214 |
+
|
| 215 |
+
# Check for help command
|
| 216 |
+
if user_query.lower() == 'help':
|
| 217 |
+
print("\n📋 Example questions you can ask:")
|
| 218 |
+
print(" • Is heart surgery covered under this policy?")
|
| 219 |
+
print(" • What is the waiting period for pre-existing diseases?")
|
| 220 |
+
print(" • Can I claim for dental treatment?")
|
| 221 |
+
print(" • What is the maximum coverage amount?")
|
| 222 |
+
print(" • Are there any exclusions for chronic diseases?")
|
| 223 |
+
print(" • What documents are required for claim submission?")
|
| 224 |
+
print(" • Is cancer treatment covered?")
|
| 225 |
+
print(" • What is the claim process?")
|
| 226 |
+
print()
|
| 227 |
+
continue
|
| 228 |
+
|
| 229 |
+
# Skip empty queries
|
| 230 |
+
if not user_query:
|
| 231 |
+
print("⚠️ Please enter a question.")
|
| 232 |
+
continue
|
| 233 |
+
|
| 234 |
+
query_count += 1
|
| 235 |
+
print_subsection(f"Processing Query #{query_count}")
|
| 236 |
+
print(f"🤔 Query: {user_query}")
|
| 237 |
+
|
| 238 |
+
# Process the query
|
| 239 |
+
start_time = time.time()
|
| 240 |
+
result = rag_system.process_query(user_query)
|
| 241 |
+
processing_time = time.time() - start_time
|
| 242 |
+
|
| 243 |
+
# Display results
|
| 244 |
+
print(f"⏱️ Processing time: {processing_time:.2f} seconds")
|
| 245 |
+
print(f"📋 Decision: {result.decision.upper()}")
|
| 246 |
+
print(f"🎯 Confidence: {result.confidence_score:.2%}")
|
| 247 |
+
|
| 248 |
+
if result.amount:
|
| 249 |
+
print(f"💰 Amount: ₹{result.amount:,.2f}")
|
| 250 |
+
|
| 251 |
+
print(f"📝 Justification: {result.justification}")
|
| 252 |
+
|
| 253 |
+
if result.relevant_clauses:
|
| 254 |
+
print(f"📄 Relevant Clauses: {', '.join(result.relevant_clauses)}")
|
| 255 |
+
|
| 256 |
+
results.append({
|
| 257 |
+
"query": user_query,
|
| 258 |
+
"result": result,
|
| 259 |
+
"processing_time": processing_time
|
| 260 |
+
})
|
| 261 |
+
|
| 262 |
+
print()
|
| 263 |
+
|
| 264 |
+
# Ask if user wants to continue
|
| 265 |
+
if query_count % 3 == 0: # Ask every 3 queries
|
| 266 |
+
continue_choice = input("❓ Continue asking questions? (y/n): ").strip().lower()
|
| 267 |
+
if continue_choice not in ['y', 'yes', '']:
|
| 268 |
+
print("👋 Thanks for using the RAG system!")
|
| 269 |
+
break
|
| 270 |
+
|
| 271 |
+
except KeyboardInterrupt:
|
| 272 |
+
print("\n👋 Interrupted by user. Goodbye!")
|
| 273 |
+
break
|
| 274 |
+
except Exception as e:
|
| 275 |
+
print(f"❌ Error processing query: {e}")
|
| 276 |
+
print("💡 Try asking a different question or type 'help' for examples.")
|
| 277 |
+
print()
|
| 278 |
+
|
| 279 |
+
return results
|
| 280 |
+
|
| 281 |
+
def demo_audit_trail(rag_system):
|
| 282 |
+
"""Demo audit trail functionality"""
|
| 283 |
+
print_section("AUDIT TRAIL DEMO")
|
| 284 |
+
|
| 285 |
+
try:
|
| 286 |
+
# Get audit trail
|
| 287 |
+
audit_log = rag_system.get_audit_trail()
|
| 288 |
+
|
| 289 |
+
print_subsection("Audit Trail Overview")
|
| 290 |
+
print(f"📊 Total queries processed: {len(audit_log)}")
|
| 291 |
+
|
| 292 |
+
if audit_log:
|
| 293 |
+
print("\n📋 Recent audit entries:")
|
| 294 |
+
for i, entry in enumerate(audit_log[-3:], 1): # Show last 3 entries
|
| 295 |
+
print(f" Entry {i}:")
|
| 296 |
+
print(f" Timestamp: {entry.get('timestamp', 'N/A')}")
|
| 297 |
+
print(f" Query: {entry.get('query', 'N/A')}")
|
| 298 |
+
print(f" Relevant chunks: {entry.get('relevant_chunks_count', 0)}")
|
| 299 |
+
|
| 300 |
+
if 'error' in entry:
|
| 301 |
+
print(f" Error: {entry['error']}")
|
| 302 |
+
print()
|
| 303 |
+
|
| 304 |
+
# Save audit trail
|
| 305 |
+
print_subsection("Saving Audit Trail")
|
| 306 |
+
audit_file = "demo_audit_trail.json"
|
| 307 |
+
rag_system.save_audit_trail(audit_file)
|
| 308 |
+
print(f"✅ Audit trail saved to: {audit_file}")
|
| 309 |
+
|
| 310 |
+
# Show audit file size
|
| 311 |
+
if os.path.exists(audit_file):
|
| 312 |
+
file_size = os.path.getsize(audit_file)
|
| 313 |
+
print(f"📁 File size: {file_size:,} bytes")
|
| 314 |
+
|
| 315 |
+
except Exception as e:
|
| 316 |
+
print(f"❌ Error with audit trail: {e}")
|
| 317 |
+
|
| 318 |
+
def demo_system_analysis(rag_system, query_results):
|
| 319 |
+
"""Demo system performance and analysis"""
|
| 320 |
+
print_section("SYSTEM ANALYSIS DEMO")
|
| 321 |
+
|
| 322 |
+
if not query_results:
|
| 323 |
+
print("❌ No query results to analyze")
|
| 324 |
+
return
|
| 325 |
+
|
| 326 |
+
print_subsection("Performance Statistics")
|
| 327 |
+
|
| 328 |
+
# Calculate statistics
|
| 329 |
+
total_queries = len(query_results)
|
| 330 |
+
avg_processing_time = sum(r['processing_time'] for r in query_results) / total_queries
|
| 331 |
+
avg_confidence = sum(r['result'].confidence_score for r in query_results) / total_queries
|
| 332 |
+
|
| 333 |
+
decisions = [r['result'].decision for r in query_results]
|
| 334 |
+
decision_counts = {}
|
| 335 |
+
for decision in decisions:
|
| 336 |
+
decision_counts[decision] = decision_counts.get(decision, 0) + 1
|
| 337 |
+
|
| 338 |
+
print(f"📊 Total queries processed: {total_queries}")
|
| 339 |
+
print(f"⏱️ Average processing time: {avg_processing_time:.2f} seconds")
|
| 340 |
+
print(f"🎯 Average confidence score: {avg_confidence:.2%}")
|
| 341 |
+
|
| 342 |
+
print("\n📋 Decision Distribution:")
|
| 343 |
+
for decision, count in decision_counts.items():
|
| 344 |
+
percentage = (count / total_queries) * 100
|
| 345 |
+
print(f" {decision.upper()}: {count} ({percentage:.1f}%)")
|
| 346 |
+
|
| 347 |
+
print_subsection("Query Analysis")
|
| 348 |
+
|
| 349 |
+
# Find best and worst performing queries
|
| 350 |
+
best_query = max(query_results, key=lambda x: x['result'].confidence_score)
|
| 351 |
+
worst_query = min(query_results, key=lambda x: x['result'].confidence_score)
|
| 352 |
+
|
| 353 |
+
print(f"🏆 Best performing query:")
|
| 354 |
+
print(f" Query: {best_query['query']}")
|
| 355 |
+
print(f" Confidence: {best_query['result'].confidence_score:.2%}")
|
| 356 |
+
|
| 357 |
+
print(f"\n⚠️ Worst performing query:")
|
| 358 |
+
print(f" Query: {worst_query['query']}")
|
| 359 |
+
print(f" Confidence: {worst_query['result'].confidence_score:.2%}")
|
| 360 |
+
|
| 361 |
+
def demo_advanced_features():
|
| 362 |
+
"""Demo advanced features like hybrid search and contextual compression"""
|
| 363 |
+
print_section("ADVANCED FEATURES DEMO")
|
| 364 |
+
|
| 365 |
+
print_subsection("Vector Database Features")
|
| 366 |
+
print("🔍 Semantic search with contextual compression")
|
| 367 |
+
print("📚 Document chunking with metadata preservation")
|
| 368 |
+
print("🎯 Similarity scoring and ranking")
|
| 369 |
+
|
| 370 |
+
print_subsection("LLM Integration")
|
| 371 |
+
print("🧠 Query parsing with entity extraction")
|
| 372 |
+
print("💭 Reasoning with policy clause mapping")
|
| 373 |
+
print("📋 Structured JSON response generation")
|
| 374 |
+
|
| 375 |
+
print_subsection("Audit and Compliance")
|
| 376 |
+
print("📝 Complete audit trail with timestamps")
|
| 377 |
+
print("🔗 Decision justification with clause references")
|
| 378 |
+
print("💾 Exportable audit logs for compliance")
|
| 379 |
+
|
| 380 |
+
def main():
|
| 381 |
+
"""Main demo function"""
|
| 382 |
+
print("🚀 RAG Insurance Policy Analyzer - Interactive GPU Demo")
|
| 383 |
+
print("This demo allows you to upload PDF documents from anywhere on your system")
|
| 384 |
+
print("and ask questions interactively. Powered by GPU-accelerated RAG system")
|
| 385 |
+
|
| 386 |
+
# Step 1: Document Ingestion
|
| 387 |
+
rag_system = demo_document_ingestion()
|
| 388 |
+
if not rag_system:
|
| 389 |
+
print("❌ Demo cannot continue without successful document ingestion")
|
| 390 |
+
return
|
| 391 |
+
|
| 392 |
+
# Step 2: Query Processing
|
| 393 |
+
query_results = demo_query_processing(rag_system)
|
| 394 |
+
|
| 395 |
+
# Step 3: Audit Trail
|
| 396 |
+
demo_audit_trail(rag_system)
|
| 397 |
+
|
| 398 |
+
# Step 4: System Analysis
|
| 399 |
+
demo_system_analysis(rag_system, query_results)
|
| 400 |
+
|
| 401 |
+
# Step 5: Advanced Features
|
| 402 |
+
demo_advanced_features()
|
| 403 |
+
|
| 404 |
+
print_section("INTERACTIVE DEMO COMPLETED")
|
| 405 |
+
print("✅ You've successfully used the interactive RAG system!")
|
| 406 |
+
print("📁 Check the following files for outputs:")
|
| 407 |
+
print(" - demo_audit_trail.json (audit trail)")
|
| 408 |
+
print(" - vector_db/ (vector database)")
|
| 409 |
+
print(" - uploads/ (uploaded documents)")
|
| 410 |
+
|
| 411 |
+
print("\n🎯 Next Steps:")
|
| 412 |
+
print(" 1. Start the web interface: python api_server.py")
|
| 413 |
+
print(" 2. Open http://localhost:8000 in your browser")
|
| 414 |
+
print(" 3. Upload documents and process queries interactively")
|
| 415 |
+
print(" 4. Or run this demo again: python demo.py")
|
| 416 |
+
print(" 5. Try different PDF files from anywhere on your system")
|
| 417 |
+
|
| 418 |
+
if __name__ == "__main__":
|
| 419 |
+
main()
|
doc2_processed_results.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
document_processer.py
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Advanced Document Processor for Multi-Format Document Ingestion
|
| 3 |
+
Handles PDF, TXT, email, and other document types with OCR and table extraction
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import hashlib
|
| 9 |
+
import logging
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import email
|
| 15 |
+
import json
|
| 16 |
+
|
| 17 |
+
# Document processing libraries
|
| 18 |
+
import fitz # PyMuPDF
|
| 19 |
+
try:
|
| 20 |
+
import pytesseract
|
| 21 |
+
from PIL import Image
|
| 22 |
+
import cv2
|
| 23 |
+
import numpy as np
|
| 24 |
+
OCR_AVAILABLE = True
|
| 25 |
+
except ImportError:
|
| 26 |
+
OCR_AVAILABLE = False
|
| 27 |
+
print("⚠️ pytesseract not available. OCR functionality will be disabled.")
|
| 28 |
+
from pdf2image import convert_from_path
|
| 29 |
+
from docx import Document
|
| 30 |
+
from bs4 import BeautifulSoup
|
| 31 |
+
import requests
|
| 32 |
+
import pandas as pd
|
| 33 |
+
# Table extraction libraries
|
| 34 |
+
try:
|
| 35 |
+
import tabula
|
| 36 |
+
import camelot
|
| 37 |
+
TABLE_EXTRACTION_AVAILABLE = True
|
| 38 |
+
except ImportError:
|
| 39 |
+
TABLE_EXTRACTION_AVAILABLE = False
|
| 40 |
+
print("⚠️ Table extraction libraries not available. Table extraction will be disabled.")
|
| 41 |
+
|
| 42 |
+
# LangChain for text splitting
|
| 43 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 44 |
+
from langchain.schema import Document
|
| 45 |
+
|
| 46 |
+
# Configure logging
|
| 47 |
+
logging.basicConfig(level=logging.INFO)
|
| 48 |
+
logger = logging.getLogger(__name__)
|
| 49 |
+
|
| 50 |
+
@dataclass
|
| 51 |
+
class DocumentChunk:
|
| 52 |
+
"""Represents a processed document chunk with metadata"""
|
| 53 |
+
chunk_id: str
|
| 54 |
+
content: str
|
| 55 |
+
source_file: str
|
| 56 |
+
file_type: str
|
| 57 |
+
page_number: Optional[int] = None
|
| 58 |
+
chunk_index: Optional[int] = None
|
| 59 |
+
section_type: Optional[str] = None # text, table, header, footer, etc.
|
| 60 |
+
confidence_score: Optional[float] = None
|
| 61 |
+
extracted_entities: Optional[Dict[str, Any]] = None
|
| 62 |
+
table_data: Optional[Dict[str, Any]] = None
|
| 63 |
+
embedding: Optional[List[float]] = None
|
| 64 |
+
|
| 65 |
+
class AdvancedDocumentProcessor:
|
| 66 |
+
"""Advanced document processor with multi-format support and table extraction"""
|
| 67 |
+
|
| 68 |
+
def __init__(self, ocr_language='eng', chunk_size=1000, chunk_overlap=200):
|
| 69 |
+
self.ocr_language = ocr_language
|
| 70 |
+
self.chunk_size = chunk_size
|
| 71 |
+
self.chunk_overlap = chunk_overlap
|
| 72 |
+
|
| 73 |
+
# Initialize text splitter
|
| 74 |
+
self.text_splitter = RecursiveCharacterTextSplitter(
|
| 75 |
+
chunk_size=chunk_size,
|
| 76 |
+
chunk_overlap=chunk_overlap,
|
| 77 |
+
separators=["\n\n", "\n", ". ", " ", ""]
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Supported file types
|
| 81 |
+
self.supported_extensions = {
|
| 82 |
+
'.pdf': self._process_pdf,
|
| 83 |
+
'.txt': self._process_txt,
|
| 84 |
+
'.docx': self._process_docx,
|
| 85 |
+
'.html': self._process_html,
|
| 86 |
+
'.htm': self._process_html,
|
| 87 |
+
'.eml': self._process_email,
|
| 88 |
+
'.msg': self._process_email,
|
| 89 |
+
'.csv': self._process_csv,
|
| 90 |
+
'.json': self._process_json
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
def process_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
|
| 94 |
+
"""Main entry point for document processing"""
|
| 95 |
+
try:
|
| 96 |
+
file_path = Path(file_path)
|
| 97 |
+
|
| 98 |
+
if not file_path.exists():
|
| 99 |
+
raise FileNotFoundError(f"File not found: {file_path}")
|
| 100 |
+
|
| 101 |
+
file_extension = file_path.suffix.lower()
|
| 102 |
+
|
| 103 |
+
if file_extension not in self.supported_extensions:
|
| 104 |
+
raise ValueError(f"Unsupported file type: {file_extension}")
|
| 105 |
+
|
| 106 |
+
logger.info(f"Processing document: {file_path}")
|
| 107 |
+
|
| 108 |
+
# Process based on file type
|
| 109 |
+
processor_func = self.supported_extensions[file_extension]
|
| 110 |
+
raw_content = processor_func(str(file_path), use_ocr)
|
| 111 |
+
|
| 112 |
+
# Extract tables and structured content
|
| 113 |
+
structured_content = self._extract_structured_content(raw_content, file_path)
|
| 114 |
+
|
| 115 |
+
# Chunk the content
|
| 116 |
+
chunks = self._chunk_content(structured_content, str(file_path), file_extension)
|
| 117 |
+
|
| 118 |
+
logger.info(f"Successfully processed {len(chunks)} chunks from {file_path}")
|
| 119 |
+
return chunks
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
logger.error(f"Error processing document {file_path}: {e}")
|
| 123 |
+
logger.error("This might be due to missing dependencies or OCR issues.")
|
| 124 |
+
logger.error("Please check:")
|
| 125 |
+
logger.error("1. File exists and is readable")
|
| 126 |
+
logger.error("2. Tesseract is installed (run: python fix_tesseract.py)")
|
| 127 |
+
logger.error("3. All dependencies are installed (run: python install_nltk_version.py)")
|
| 128 |
+
logger.error("4. Run debug_document_processing.py to diagnose issues")
|
| 129 |
+
import traceback
|
| 130 |
+
logger.error(f"Full error traceback: {traceback.format_exc()}")
|
| 131 |
+
raise
|
| 132 |
+
|
| 133 |
+
def _process_pdf(self, pdf_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 134 |
+
"""Process PDF with text extraction, OCR, and table detection"""
|
| 135 |
+
try:
|
| 136 |
+
doc = fitz.open(pdf_path)
|
| 137 |
+
content = {
|
| 138 |
+
'text': "",
|
| 139 |
+
'tables': [],
|
| 140 |
+
'images': [],
|
| 141 |
+
'metadata': {}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
for page_num in range(len(doc)):
|
| 145 |
+
page = doc.load_page(page_num)
|
| 146 |
+
|
| 147 |
+
# Extract text normally first
|
| 148 |
+
page_text = page.get_text()
|
| 149 |
+
|
| 150 |
+
# If no text or very little text, use OCR
|
| 151 |
+
if use_ocr or len(page_text.strip()) < 50:
|
| 152 |
+
logger.info(f"Using OCR for page {page_num + 1}")
|
| 153 |
+
ocr_text = self._extract_text_with_ocr(page, page_num)
|
| 154 |
+
if ocr_text.strip(): # Only use OCR text if it's not empty
|
| 155 |
+
page_text = ocr_text
|
| 156 |
+
else:
|
| 157 |
+
logger.warning(f"OCR returned empty text for page {page_num + 1}, using normal extraction")
|
| 158 |
+
|
| 159 |
+
content['text'] += f"\n\n--- Page {page_num + 1} ---\n{page_text}"
|
| 160 |
+
|
| 161 |
+
# Extract tables from this page
|
| 162 |
+
page_tables = self._extract_tables_from_page(page, page_num)
|
| 163 |
+
content['tables'].extend(page_tables)
|
| 164 |
+
|
| 165 |
+
# Extract images (for future OCR if needed)
|
| 166 |
+
page_images = self._extract_images_from_page(page, page_num)
|
| 167 |
+
content['images'].extend(page_images)
|
| 168 |
+
|
| 169 |
+
doc.close()
|
| 170 |
+
return content
|
| 171 |
+
|
| 172 |
+
except Exception as e:
|
| 173 |
+
logger.error(f"Error processing PDF {pdf_path}: {e}")
|
| 174 |
+
raise
|
| 175 |
+
|
| 176 |
+
def _extract_text_with_ocr(self, page, page_num: int) -> str:
|
| 177 |
+
"""Extract text using OCR with pytesseract"""
|
| 178 |
+
if not OCR_AVAILABLE:
|
| 179 |
+
logger.error("pytesseract not available. Please install it with: pip install pytesseract")
|
| 180 |
+
return ""
|
| 181 |
+
|
| 182 |
+
try:
|
| 183 |
+
# Get page as image
|
| 184 |
+
pix = page.get_pixmap()
|
| 185 |
+
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
| 186 |
+
|
| 187 |
+
# Simple OCR without complex preprocessing
|
| 188 |
+
text = pytesseract.image_to_string(
|
| 189 |
+
img,
|
| 190 |
+
lang=self.ocr_language,
|
| 191 |
+
config='--psm 6' # Assume uniform block of text
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
return text
|
| 195 |
+
|
| 196 |
+
except Exception as e:
|
| 197 |
+
logger.error(f"OCR error on page {page_num}: {e}")
|
| 198 |
+
logger.error("This might be due to Tesseract not being installed or configured properly.")
|
| 199 |
+
logger.error("Please run: python fix_tesseract.py")
|
| 200 |
+
return ""
|
| 201 |
+
|
| 202 |
+
def _extract_tables_from_page(self, page, page_num: int) -> List[Dict[str, Any]]:
|
| 203 |
+
"""Extract tables from PDF page using basic methods"""
|
| 204 |
+
tables = []
|
| 205 |
+
|
| 206 |
+
try:
|
| 207 |
+
if TABLE_EXTRACTION_AVAILABLE:
|
| 208 |
+
# Method 1: Use camelot for table extraction
|
| 209 |
+
try:
|
| 210 |
+
page_tables = camelot.read_pdf(
|
| 211 |
+
page.parent,
|
| 212 |
+
pages=str(page_num + 1),
|
| 213 |
+
flavor='lattice'
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
for table in page_tables:
|
| 217 |
+
if table.df.shape[0] > 1: # Only include tables with data
|
| 218 |
+
tables.append({
|
| 219 |
+
'page': page_num + 1,
|
| 220 |
+
'data': table.df.to_dict('records'),
|
| 221 |
+
'accuracy': table.accuracy,
|
| 222 |
+
'whitespace': table.whitespace,
|
| 223 |
+
'method': 'camelot'
|
| 224 |
+
})
|
| 225 |
+
except Exception as e:
|
| 226 |
+
logger.debug(f"Camelot table extraction failed: {e}")
|
| 227 |
+
|
| 228 |
+
# Method 2: Use tabula for simpler tables
|
| 229 |
+
try:
|
| 230 |
+
tabula_tables = tabula.read_pdf(
|
| 231 |
+
page.parent,
|
| 232 |
+
pages=page_num + 1,
|
| 233 |
+
multiple_tables=True
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
for i, table in enumerate(tabula_tables):
|
| 237 |
+
if not table.empty:
|
| 238 |
+
tables.append({
|
| 239 |
+
'page': page_num + 1,
|
| 240 |
+
'data': table.to_dict('records'),
|
| 241 |
+
'method': 'tabula'
|
| 242 |
+
})
|
| 243 |
+
except Exception as e:
|
| 244 |
+
logger.debug(f"Tabula table extraction failed: {e}")
|
| 245 |
+
|
| 246 |
+
# Method 3: Basic table extraction using text patterns (fallback)
|
| 247 |
+
if not tables:
|
| 248 |
+
tables = self._extract_tables_basic(page, page_num)
|
| 249 |
+
|
| 250 |
+
except Exception as e:
|
| 251 |
+
logger.error(f"Table extraction error on page {page_num}: {e}")
|
| 252 |
+
|
| 253 |
+
return tables
|
| 254 |
+
|
| 255 |
+
def _extract_images_from_page(self, page, page_num: int) -> List[Dict[str, Any]]:
|
| 256 |
+
"""Extract images from PDF page"""
|
| 257 |
+
images = []
|
| 258 |
+
|
| 259 |
+
try:
|
| 260 |
+
image_list = page.get_images()
|
| 261 |
+
|
| 262 |
+
for img_index, img in enumerate(image_list):
|
| 263 |
+
xref = img[0]
|
| 264 |
+
pix = fitz.Pixmap(page.parent, xref)
|
| 265 |
+
|
| 266 |
+
if pix.n - pix.alpha < 4: # GRAY or RGB
|
| 267 |
+
images.append({
|
| 268 |
+
'page': page_num + 1,
|
| 269 |
+
'index': img_index,
|
| 270 |
+
'width': pix.width,
|
| 271 |
+
'height': pix.height,
|
| 272 |
+
'format': pix.colorspace.name
|
| 273 |
+
})
|
| 274 |
+
|
| 275 |
+
pix = None # Free memory
|
| 276 |
+
|
| 277 |
+
except Exception as e:
|
| 278 |
+
logger.error(f"Image extraction error on page {page_num}: {e}")
|
| 279 |
+
|
| 280 |
+
return images
|
| 281 |
+
|
| 282 |
+
def _extract_tables_basic(self, page, page_num: int) -> List[Dict[str, Any]]:
|
| 283 |
+
"""Basic table extraction using text patterns when advanced libraries are not available"""
|
| 284 |
+
tables = []
|
| 285 |
+
|
| 286 |
+
try:
|
| 287 |
+
# Get page text
|
| 288 |
+
page_text = page.get_text()
|
| 289 |
+
|
| 290 |
+
# Simple table detection using common patterns
|
| 291 |
+
lines = page_text.split('\n')
|
| 292 |
+
table_lines = []
|
| 293 |
+
in_table = False
|
| 294 |
+
|
| 295 |
+
for line in lines:
|
| 296 |
+
# Check for table-like patterns (multiple columns separated by spaces/tabs)
|
| 297 |
+
if len(line.strip()) > 0 and ('\t' in line or line.count(' ') >= 2):
|
| 298 |
+
if not in_table:
|
| 299 |
+
in_table = True
|
| 300 |
+
table_lines = []
|
| 301 |
+
table_lines.append(line)
|
| 302 |
+
elif in_table:
|
| 303 |
+
# End of table detected
|
| 304 |
+
if table_lines:
|
| 305 |
+
# Convert to table format
|
| 306 |
+
table_data = []
|
| 307 |
+
for table_line in table_lines:
|
| 308 |
+
# Split by multiple spaces or tabs
|
| 309 |
+
columns = [col.strip() for col in table_line.split('\t') if col.strip()]
|
| 310 |
+
if not columns:
|
| 311 |
+
columns = [col.strip() for col in line.split(' ') if col.strip()]
|
| 312 |
+
if columns:
|
| 313 |
+
table_data.append(columns)
|
| 314 |
+
|
| 315 |
+
if table_data and len(table_data) > 1:
|
| 316 |
+
tables.append({
|
| 317 |
+
'page': page_num + 1,
|
| 318 |
+
'data': table_data,
|
| 319 |
+
'method': 'basic_pattern'
|
| 320 |
+
})
|
| 321 |
+
|
| 322 |
+
in_table = False
|
| 323 |
+
table_lines = []
|
| 324 |
+
|
| 325 |
+
# Handle table at end of page
|
| 326 |
+
if in_table and table_lines:
|
| 327 |
+
table_data = []
|
| 328 |
+
for table_line in table_lines:
|
| 329 |
+
columns = [col.strip() for col in table_line.split('\t') if col.strip()]
|
| 330 |
+
if not columns:
|
| 331 |
+
columns = [col.strip() for col in line.split(' ') if col.strip()]
|
| 332 |
+
if columns:
|
| 333 |
+
table_data.append(columns)
|
| 334 |
+
|
| 335 |
+
if table_data and len(table_data) > 1:
|
| 336 |
+
tables.append({
|
| 337 |
+
'page': page_num + 1,
|
| 338 |
+
'data': table_data,
|
| 339 |
+
'method': 'basic_pattern'
|
| 340 |
+
})
|
| 341 |
+
|
| 342 |
+
except Exception as e:
|
| 343 |
+
logger.error(f"Basic table extraction error on page {page_num}: {e}")
|
| 344 |
+
|
| 345 |
+
return tables
|
| 346 |
+
|
| 347 |
+
def _process_txt(self, txt_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 348 |
+
"""Process plain text files"""
|
| 349 |
+
try:
|
| 350 |
+
with open(txt_path, 'r', encoding='utf-8') as f:
|
| 351 |
+
text = f.read()
|
| 352 |
+
|
| 353 |
+
return {
|
| 354 |
+
'text': text,
|
| 355 |
+
'tables': [],
|
| 356 |
+
'images': [],
|
| 357 |
+
'metadata': {}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
except Exception as e:
|
| 361 |
+
logger.error(f"Error processing TXT {txt_path}: {e}")
|
| 362 |
+
raise
|
| 363 |
+
|
| 364 |
+
def _process_docx(self, docx_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 365 |
+
"""Process Word documents"""
|
| 366 |
+
try:
|
| 367 |
+
doc = Document(docx_path)
|
| 368 |
+
text = ""
|
| 369 |
+
tables = []
|
| 370 |
+
|
| 371 |
+
# Extract text from paragraphs - handle different versions of python-docx
|
| 372 |
+
try:
|
| 373 |
+
# Try the standard way first
|
| 374 |
+
for paragraph in doc.paragraphs:
|
| 375 |
+
text += paragraph.text + "\n"
|
| 376 |
+
except AttributeError:
|
| 377 |
+
# Fallback for different versions
|
| 378 |
+
try:
|
| 379 |
+
# Try alternative attribute names
|
| 380 |
+
if hasattr(doc, 'paragraphs'):
|
| 381 |
+
for paragraph in doc.paragraphs:
|
| 382 |
+
text += paragraph.text + "\n"
|
| 383 |
+
elif hasattr(doc, 'content'):
|
| 384 |
+
text = doc.content
|
| 385 |
+
else:
|
| 386 |
+
# Last resort - try to extract text from the document structure
|
| 387 |
+
text = str(doc)
|
| 388 |
+
except Exception as e:
|
| 389 |
+
logger.warning(f"Could not extract text from DOCX paragraphs: {e}")
|
| 390 |
+
text = "DOCX content could not be extracted"
|
| 391 |
+
|
| 392 |
+
# Extract tables from Word document
|
| 393 |
+
try:
|
| 394 |
+
# Check if tables attribute exists
|
| 395 |
+
if hasattr(doc, 'tables'):
|
| 396 |
+
for table in doc.tables:
|
| 397 |
+
table_data = []
|
| 398 |
+
for row in table.rows:
|
| 399 |
+
row_data = [cell.text for cell in row.cells]
|
| 400 |
+
table_data.append(row_data)
|
| 401 |
+
|
| 402 |
+
if table_data:
|
| 403 |
+
tables.append({
|
| 404 |
+
'data': table_data,
|
| 405 |
+
'method': 'docx'
|
| 406 |
+
})
|
| 407 |
+
else:
|
| 408 |
+
logger.warning("DOCX document has no 'tables' attribute - tables will not be extracted")
|
| 409 |
+
except Exception as e:
|
| 410 |
+
logger.warning(f"Could not extract tables from DOCX: {e}")
|
| 411 |
+
|
| 412 |
+
return {
|
| 413 |
+
'text': text,
|
| 414 |
+
'tables': tables,
|
| 415 |
+
'images': [],
|
| 416 |
+
'metadata': {}
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
except Exception as e:
|
| 420 |
+
logger.error(f"Error processing DOCX {docx_path}: {e}")
|
| 421 |
+
raise
|
| 422 |
+
|
| 423 |
+
def _process_html(self, html_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 424 |
+
"""Process HTML files"""
|
| 425 |
+
try:
|
| 426 |
+
with open(html_path, 'r', encoding='utf-8') as f:
|
| 427 |
+
html_content = f.read()
|
| 428 |
+
|
| 429 |
+
soup = BeautifulSoup(html_content, 'html.parser')
|
| 430 |
+
|
| 431 |
+
# Extract text
|
| 432 |
+
text = soup.get_text(separator='\n', strip=True)
|
| 433 |
+
|
| 434 |
+
# Extract tables
|
| 435 |
+
tables = []
|
| 436 |
+
for table in soup.find_all('table'):
|
| 437 |
+
table_data = []
|
| 438 |
+
for row in table.find_all('tr'):
|
| 439 |
+
row_data = [cell.get_text(strip=True) for cell in row.find_all(['td', 'th'])]
|
| 440 |
+
if row_data:
|
| 441 |
+
table_data.append(row_data)
|
| 442 |
+
|
| 443 |
+
if table_data:
|
| 444 |
+
tables.append({
|
| 445 |
+
'data': table_data,
|
| 446 |
+
'method': 'html'
|
| 447 |
+
})
|
| 448 |
+
|
| 449 |
+
return {
|
| 450 |
+
'text': text,
|
| 451 |
+
'tables': tables,
|
| 452 |
+
'images': [],
|
| 453 |
+
'metadata': {}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
except Exception as e:
|
| 457 |
+
logger.error(f"Error processing HTML {html_path}: {e}")
|
| 458 |
+
raise
|
| 459 |
+
|
| 460 |
+
def _process_email(self, email_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 461 |
+
"""Process email files (.eml, .msg)"""
|
| 462 |
+
try:
|
| 463 |
+
with open(email_path, 'r', encoding='utf-8') as f:
|
| 464 |
+
email_content = f.read()
|
| 465 |
+
|
| 466 |
+
# Parse email
|
| 467 |
+
msg = email.message_from_string(email_content)
|
| 468 |
+
|
| 469 |
+
# Extract email components
|
| 470 |
+
subject = msg.get('Subject', '')
|
| 471 |
+
sender = msg.get('From', '')
|
| 472 |
+
recipient = msg.get('To', '')
|
| 473 |
+
date = msg.get('Date', '')
|
| 474 |
+
|
| 475 |
+
# Extract body
|
| 476 |
+
body = ""
|
| 477 |
+
if msg.is_multipart():
|
| 478 |
+
for part in msg.walk():
|
| 479 |
+
if part.get_content_type() == "text/plain":
|
| 480 |
+
body = part.get_payload(decode=True).decode()
|
| 481 |
+
break
|
| 482 |
+
else:
|
| 483 |
+
body = msg.get_payload(decode=True).decode()
|
| 484 |
+
|
| 485 |
+
# Combine all text
|
| 486 |
+
text = f"Subject: {subject}\nFrom: {sender}\nTo: {recipient}\nDate: {date}\n\n{body}"
|
| 487 |
+
|
| 488 |
+
return {
|
| 489 |
+
'text': text,
|
| 490 |
+
'tables': [],
|
| 491 |
+
'images': [],
|
| 492 |
+
'metadata': {
|
| 493 |
+
'subject': subject,
|
| 494 |
+
'sender': sender,
|
| 495 |
+
'recipient': recipient,
|
| 496 |
+
'date': date
|
| 497 |
+
}
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
except Exception as e:
|
| 501 |
+
logger.error(f"Error processing email {email_path}: {e}")
|
| 502 |
+
raise
|
| 503 |
+
|
| 504 |
+
def _process_csv(self, csv_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 505 |
+
"""Process CSV files"""
|
| 506 |
+
try:
|
| 507 |
+
df = pd.read_csv(csv_path)
|
| 508 |
+
|
| 509 |
+
# Convert to text representation
|
| 510 |
+
text = df.to_string(index=False)
|
| 511 |
+
|
| 512 |
+
# Store as table
|
| 513 |
+
tables = [{
|
| 514 |
+
'data': df.to_dict('records'),
|
| 515 |
+
'method': 'csv'
|
| 516 |
+
}]
|
| 517 |
+
|
| 518 |
+
return {
|
| 519 |
+
'text': text,
|
| 520 |
+
'tables': tables,
|
| 521 |
+
'images': [],
|
| 522 |
+
'metadata': {
|
| 523 |
+
'columns': list(df.columns),
|
| 524 |
+
'rows': len(df)
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
except Exception as e:
|
| 529 |
+
logger.error(f"Error processing CSV {csv_path}: {e}")
|
| 530 |
+
raise
|
| 531 |
+
|
| 532 |
+
def _process_json(self, json_path: str, use_ocr: bool = False) -> Dict[str, Any]:
|
| 533 |
+
"""Process JSON files"""
|
| 534 |
+
try:
|
| 535 |
+
with open(json_path, 'r', encoding='utf-8') as f:
|
| 536 |
+
data = json.load(f)
|
| 537 |
+
|
| 538 |
+
# Convert to text representation
|
| 539 |
+
text = json.dumps(data, indent=2)
|
| 540 |
+
|
| 541 |
+
return {
|
| 542 |
+
'text': text,
|
| 543 |
+
'tables': [],
|
| 544 |
+
'images': [],
|
| 545 |
+
'metadata': {
|
| 546 |
+
'type': 'json',
|
| 547 |
+
'keys': list(data.keys()) if isinstance(data, dict) else []
|
| 548 |
+
}
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
except Exception as e:
|
| 552 |
+
logger.error(f"Error processing JSON {json_path}: {e}")
|
| 553 |
+
raise
|
| 554 |
+
|
| 555 |
+
def _extract_structured_content(self, content: Dict[str, Any], file_path: Path) -> List[Dict[str, Any]]:
|
| 556 |
+
"""Extract and structure content from processed document"""
|
| 557 |
+
structured_content = []
|
| 558 |
+
|
| 559 |
+
# Add main text content
|
| 560 |
+
if content['text'].strip():
|
| 561 |
+
structured_content.append({
|
| 562 |
+
'type': 'text',
|
| 563 |
+
'content': content['text'],
|
| 564 |
+
'section_type': 'main_text'
|
| 565 |
+
})
|
| 566 |
+
|
| 567 |
+
# Add tables
|
| 568 |
+
for i, table in enumerate(content['tables']):
|
| 569 |
+
table_text = self._table_to_text(table['data'])
|
| 570 |
+
structured_content.append({
|
| 571 |
+
'type': 'table',
|
| 572 |
+
'content': table_text,
|
| 573 |
+
'section_type': 'table',
|
| 574 |
+
'table_data': table,
|
| 575 |
+
'table_index': i
|
| 576 |
+
})
|
| 577 |
+
|
| 578 |
+
# Add metadata as text
|
| 579 |
+
if content.get('metadata'):
|
| 580 |
+
metadata_text = json.dumps(content['metadata'], indent=2)
|
| 581 |
+
structured_content.append({
|
| 582 |
+
'type': 'text',
|
| 583 |
+
'content': f"Document Metadata:\n{metadata_text}",
|
| 584 |
+
'section_type': 'metadata'
|
| 585 |
+
})
|
| 586 |
+
|
| 587 |
+
return structured_content
|
| 588 |
+
|
| 589 |
+
def _table_to_text(self, table_data: List[List[str]]) -> str:
|
| 590 |
+
"""Convert table data to readable text"""
|
| 591 |
+
if not table_data:
|
| 592 |
+
return ""
|
| 593 |
+
|
| 594 |
+
text_lines = []
|
| 595 |
+
for row in table_data:
|
| 596 |
+
text_lines.append(" | ".join(str(cell) for cell in row))
|
| 597 |
+
|
| 598 |
+
return "\n".join(text_lines)
|
| 599 |
+
|
| 600 |
+
def _chunk_content(self, structured_content: List[Dict[str, Any]], source_file: str, file_type: str) -> List[DocumentChunk]:
|
| 601 |
+
"""Chunk the structured content into DocumentChunk objects"""
|
| 602 |
+
chunks = []
|
| 603 |
+
chunk_index = 0
|
| 604 |
+
|
| 605 |
+
for section in structured_content:
|
| 606 |
+
try:
|
| 607 |
+
# Use LangChain's text splitter for better semantic chunking
|
| 608 |
+
docs = [Document(page_content=section['content'], metadata={"source": source_file})]
|
| 609 |
+
split_docs = self.text_splitter.split_documents(docs)
|
| 610 |
+
|
| 611 |
+
for i, doc in enumerate(split_docs):
|
| 612 |
+
chunk_id = f"chunk_{chunk_index+1}_{hashlib.md5(doc.page_content.encode()).hexdigest()[:8]}"
|
| 613 |
+
|
| 614 |
+
chunk = DocumentChunk(
|
| 615 |
+
chunk_id=chunk_id,
|
| 616 |
+
content=doc.page_content.strip(),
|
| 617 |
+
source_file=source_file,
|
| 618 |
+
file_type=file_type,
|
| 619 |
+
chunk_index=chunk_index,
|
| 620 |
+
section_type=section.get('section_type', 'text'),
|
| 621 |
+
table_data=section.get('table_data'),
|
| 622 |
+
confidence_score=section.get('confidence_score', 1.0)
|
| 623 |
+
)
|
| 624 |
+
|
| 625 |
+
chunks.append(chunk)
|
| 626 |
+
chunk_index += 1
|
| 627 |
+
|
| 628 |
+
except Exception as e:
|
| 629 |
+
logger.error(f"Error chunking section: {e}")
|
| 630 |
+
continue
|
| 631 |
+
|
| 632 |
+
return chunks
|
| 633 |
+
|
| 634 |
+
# Example usage
|
| 635 |
+
if __name__ == "__main__":
|
| 636 |
+
processor = AdvancedDocumentProcessor()
|
| 637 |
+
|
| 638 |
+
# Test with a PDF file
|
| 639 |
+
test_file = "sample.pdf"
|
| 640 |
+
if os.path.exists(test_file):
|
| 641 |
+
chunks = processor.process_document(test_file, use_ocr=False)
|
| 642 |
+
print(f"Processed {len(chunks)} chunks from {test_file}")
|
| 643 |
+
|
| 644 |
+
for i, chunk in enumerate(chunks[:3]):
|
| 645 |
+
print(f"\nChunk {i+1}:")
|
| 646 |
+
print(f"ID: {chunk.chunk_id}")
|
| 647 |
+
print(f"Type: {chunk.section_type}")
|
| 648 |
+
print(f"Content preview: {chunk.content[:100]}...")
|
| 649 |
+
else:
|
| 650 |
+
print(f"Test file {test_file} not found")
|
keep_alive.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Keep-alive script to prevent server auto-termination
|
| 4 |
+
Pings the Flask server every 2 minutes to keep it alive
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import time
|
| 9 |
+
import logging
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
|
| 12 |
+
# Configure logging
|
| 13 |
+
logging.basicConfig(
|
| 14 |
+
level=logging.INFO,
|
| 15 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 16 |
+
)
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
def ping_server():
|
| 20 |
+
"""Ping the Flask server"""
|
| 21 |
+
try:
|
| 22 |
+
response = requests.get("http://127.0.0.1:5000/api/keep-alive", timeout=10)
|
| 23 |
+
if response.status_code == 200:
|
| 24 |
+
logger.info("✅ Server ping successful")
|
| 25 |
+
return True
|
| 26 |
+
else:
|
| 27 |
+
logger.warning(f"⚠️ Server ping failed with status: {response.status_code}")
|
| 28 |
+
return False
|
| 29 |
+
except requests.exceptions.ConnectionError:
|
| 30 |
+
logger.error("❌ Cannot connect to server - is it running?")
|
| 31 |
+
return False
|
| 32 |
+
except Exception as e:
|
| 33 |
+
logger.error(f"❌ Ping failed: {e}")
|
| 34 |
+
return False
|
| 35 |
+
|
| 36 |
+
def main():
|
| 37 |
+
"""Main keep-alive loop"""
|
| 38 |
+
logger.info("🚀 Starting keep-alive script")
|
| 39 |
+
logger.info("This script will ping the server every 2 minutes")
|
| 40 |
+
logger.info("Press Ctrl+C to stop")
|
| 41 |
+
|
| 42 |
+
ping_count = 0
|
| 43 |
+
start_time = datetime.now()
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
while True:
|
| 47 |
+
ping_count += 1
|
| 48 |
+
current_time = datetime.now()
|
| 49 |
+
uptime = current_time - start_time
|
| 50 |
+
|
| 51 |
+
logger.info(f"Ping #{ping_count} at {current_time.strftime('%H:%M:%S')} (Uptime: {uptime})")
|
| 52 |
+
|
| 53 |
+
if ping_server():
|
| 54 |
+
logger.info("Server is alive and responding")
|
| 55 |
+
else:
|
| 56 |
+
logger.warning("Server may be having issues")
|
| 57 |
+
|
| 58 |
+
# Wait 2 minutes before next ping
|
| 59 |
+
logger.info("Sleeping for 2 minutes...")
|
| 60 |
+
time.sleep(120) # 2 minutes
|
| 61 |
+
|
| 62 |
+
except KeyboardInterrupt:
|
| 63 |
+
logger.info("👋 Keep-alive script stopped by user")
|
| 64 |
+
except Exception as e:
|
| 65 |
+
logger.error(f"Keep-alive script crashed: {e}")
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
llm_reasoning.py
ADDED
|
@@ -0,0 +1,819 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Advanced LLM Reasoning Engine for Query Analysis and Response Generation
|
| 3 |
+
Handles complex reasoning, clause referencing, and structured response generation
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import re
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
# LLM and AI libraries
|
| 16 |
+
from llama_cpp import Llama
|
| 17 |
+
from transformers import pipeline
|
| 18 |
+
import torch
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
# Configure logging
|
| 22 |
+
logging.basicConfig(level=logging.INFO)
|
| 23 |
+
logger = logging.getLogger(__name__)
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class ReasoningResult:
|
| 27 |
+
"""Represents the result of LLM reasoning"""
|
| 28 |
+
decision: str # approved, denied, pending, unclear
|
| 29 |
+
confidence_score: float
|
| 30 |
+
justification: str
|
| 31 |
+
relevant_clauses: List[str]
|
| 32 |
+
amount: Optional[float] = None
|
| 33 |
+
waiting_period: Optional[str] = None
|
| 34 |
+
conditions: List[str] = None
|
| 35 |
+
exclusions: List[str] = None
|
| 36 |
+
required_documents: List[str] = None
|
| 37 |
+
processing_time: Optional[str] = None
|
| 38 |
+
reasoning_steps: List[str] = None
|
| 39 |
+
source_references: List[Dict[str, Any]] = None
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class ClauseReference:
|
| 43 |
+
"""Represents a reference to a specific policy clause"""
|
| 44 |
+
clause_id: str
|
| 45 |
+
clause_text: str
|
| 46 |
+
relevance_score: float
|
| 47 |
+
page_number: Optional[int] = None
|
| 48 |
+
section_type: Optional[str] = None
|
| 49 |
+
|
| 50 |
+
class AdvancedLLMReasoning:
|
| 51 |
+
"""Advanced LLM reasoning engine with clause referencing and structured analysis"""
|
| 52 |
+
|
| 53 |
+
def __init__(self,
|
| 54 |
+
model_path: str = None,
|
| 55 |
+
use_gpu: bool = True,
|
| 56 |
+
max_tokens: int = 2048):
|
| 57 |
+
|
| 58 |
+
self.model_path = model_path
|
| 59 |
+
self.use_gpu = use_gpu
|
| 60 |
+
self.max_tokens = max_tokens
|
| 61 |
+
|
| 62 |
+
# Initialize LLM
|
| 63 |
+
self._initialize_llm()
|
| 64 |
+
|
| 65 |
+
# Initialize reasoning patterns
|
| 66 |
+
self._initialize_reasoning_patterns()
|
| 67 |
+
|
| 68 |
+
# Initialize clause extraction
|
| 69 |
+
self._initialize_clause_extraction()
|
| 70 |
+
|
| 71 |
+
logger.info("Advanced LLM Reasoning Engine initialized")
|
| 72 |
+
|
| 73 |
+
def _initialize_llm(self):
|
| 74 |
+
"""Initialize the LLM model"""
|
| 75 |
+
try:
|
| 76 |
+
# Set default model path if none provided
|
| 77 |
+
if self.model_path is None:
|
| 78 |
+
self.model_path = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf"
|
| 79 |
+
|
| 80 |
+
# Check if model file exists
|
| 81 |
+
if not os.path.exists(self.model_path):
|
| 82 |
+
logger.warning(f"Model file not found: {self.model_path}")
|
| 83 |
+
logger.info("LLM reasoning will use fallback mode without local model")
|
| 84 |
+
self.llm = None
|
| 85 |
+
return
|
| 86 |
+
|
| 87 |
+
# Initialize Llama model with more robust configuration
|
| 88 |
+
try:
|
| 89 |
+
self.llm = Llama(
|
| 90 |
+
model_path=self.model_path,
|
| 91 |
+
n_ctx=4096,
|
| 92 |
+
n_gpu_layers=50 if self.use_gpu else 0,
|
| 93 |
+
verbose=False,
|
| 94 |
+
use_mmap=True,
|
| 95 |
+
use_mlock=False,
|
| 96 |
+
seed=42
|
| 97 |
+
)
|
| 98 |
+
logger.info(f"LLM model loaded successfully with GPU: {self.use_gpu}")
|
| 99 |
+
except Exception as gpu_error:
|
| 100 |
+
logger.warning(f"GPU loading failed: {gpu_error}")
|
| 101 |
+
# Try CPU-only configuration
|
| 102 |
+
try:
|
| 103 |
+
self.llm = Llama(
|
| 104 |
+
model_path=self.model_path,
|
| 105 |
+
n_ctx=2048,
|
| 106 |
+
n_gpu_layers=0, # CPU only
|
| 107 |
+
verbose=False,
|
| 108 |
+
use_mmap=True,
|
| 109 |
+
use_mlock=False,
|
| 110 |
+
seed=42
|
| 111 |
+
)
|
| 112 |
+
logger.info("LLM model loaded successfully with CPU configuration")
|
| 113 |
+
except Exception as cpu_error:
|
| 114 |
+
logger.error(f"CPU loading also failed: {cpu_error}")
|
| 115 |
+
self.llm = None
|
| 116 |
+
|
| 117 |
+
logger.info(f"LLM model loaded: {self.model_path}")
|
| 118 |
+
|
| 119 |
+
except Exception as e:
|
| 120 |
+
logger.error(f"Error initializing LLM: {e}")
|
| 121 |
+
logger.info("LLM reasoning will use fallback mode")
|
| 122 |
+
self.llm = None
|
| 123 |
+
|
| 124 |
+
def _initialize_reasoning_patterns(self):
|
| 125 |
+
"""Initialize reasoning patterns and templates"""
|
| 126 |
+
try:
|
| 127 |
+
# Reasoning templates for different query types and domains
|
| 128 |
+
self.reasoning_templates = {
|
| 129 |
+
'coverage_check': """
|
| 130 |
+
You are an expert document analyst. Analyze the provided document information carefully to answer the user's query.
|
| 131 |
+
|
| 132 |
+
USER QUERY: {query}
|
| 133 |
+
|
| 134 |
+
DOCUMENT SECTIONS:
|
| 135 |
+
{context}
|
| 136 |
+
|
| 137 |
+
INSTRUCTIONS:
|
| 138 |
+
1. Read and understand the document sections provided
|
| 139 |
+
2. Look for specific clauses, conditions, limitations, and exclusions
|
| 140 |
+
3. Pay attention to time periods, limits, and restrictions
|
| 141 |
+
4. Consider both what is allowed/permitted AND what is explicitly prohibited/excluded
|
| 142 |
+
5. Base your decision ONLY on the information provided in the document
|
| 143 |
+
|
| 144 |
+
ANALYSIS REQUIREMENTS:
|
| 145 |
+
- Decision: APPROVED, DENIED, CONDITIONAL, or PENDING (be precise based on document text)
|
| 146 |
+
- Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
|
| 147 |
+
- Justification: Quote specific document text and explain your reasoning
|
| 148 |
+
- Relevant Clauses: List the exact document sections that support your decision
|
| 149 |
+
- Conditions: Any specific conditions, time limits, or restrictions mentioned
|
| 150 |
+
- Exclusions: What is explicitly excluded or not permitted
|
| 151 |
+
- Required Documents: Documents mentioned as required for this type of request
|
| 152 |
+
|
| 153 |
+
IMPORTANT: If the document explicitly states something is NOT permitted or has limitations, you must reflect that in your decision. Do not assume approval unless the document clearly states it.
|
| 154 |
+
|
| 155 |
+
Respond in valid JSON format.
|
| 156 |
+
""",
|
| 157 |
+
|
| 158 |
+
'legal_compliance': """
|
| 159 |
+
You are an expert legal compliance analyst. Analyze the provided legal documents to determine compliance status.
|
| 160 |
+
|
| 161 |
+
USER QUERY: {query}
|
| 162 |
+
|
| 163 |
+
LEGAL DOCUMENT SECTIONS:
|
| 164 |
+
{context}
|
| 165 |
+
|
| 166 |
+
INSTRUCTIONS:
|
| 167 |
+
1. Read and understand the legal document sections provided
|
| 168 |
+
2. Look for specific regulations, requirements, and compliance criteria
|
| 169 |
+
3. Pay attention to deadlines, obligations, and legal requirements
|
| 170 |
+
4. Consider both what is required AND what is explicitly prohibited
|
| 171 |
+
5. Base your decision ONLY on the information provided in the legal documents
|
| 172 |
+
|
| 173 |
+
ANALYSIS REQUIREMENTS:
|
| 174 |
+
- Decision: COMPLIANT, NON_COMPLIANT, CONDITIONAL, or NEEDS_REVIEW
|
| 175 |
+
- Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
|
| 176 |
+
- Justification: Quote specific legal text and explain your reasoning
|
| 177 |
+
- Relevant Regulations: List the exact legal sections that apply
|
| 178 |
+
- Requirements: Any specific legal requirements or obligations mentioned
|
| 179 |
+
- Violations: What would constitute non-compliance
|
| 180 |
+
- Required Actions: Steps needed to achieve or maintain compliance
|
| 181 |
+
|
| 182 |
+
Respond in valid JSON format.
|
| 183 |
+
""",
|
| 184 |
+
|
| 185 |
+
'hr_policy': """
|
| 186 |
+
You are an expert HR policy analyst. Analyze the provided HR documents to answer employee-related queries.
|
| 187 |
+
|
| 188 |
+
USER QUERY: {query}
|
| 189 |
+
|
| 190 |
+
HR DOCUMENT SECTIONS:
|
| 191 |
+
{context}
|
| 192 |
+
|
| 193 |
+
INSTRUCTIONS:
|
| 194 |
+
1. Read and understand the HR document sections provided
|
| 195 |
+
2. Look for specific policies, procedures, and employee rights
|
| 196 |
+
3. Pay attention to eligibility criteria, time limits, and benefits
|
| 197 |
+
4. Consider both what is permitted AND what is explicitly prohibited
|
| 198 |
+
5. Base your decision ONLY on the information provided in the HR documents
|
| 199 |
+
|
| 200 |
+
ANALYSIS REQUIREMENTS:
|
| 201 |
+
- Decision: APPROVED, DENIED, CONDITIONAL, or PENDING_REVIEW
|
| 202 |
+
- Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
|
| 203 |
+
- Justification: Quote specific policy text and explain your reasoning
|
| 204 |
+
- Relevant Policies: List the exact policy sections that apply
|
| 205 |
+
- Eligibility: Any specific eligibility criteria or conditions
|
| 206 |
+
- Benefits: What benefits or entitlements are available
|
| 207 |
+
- Required Documentation: Documents needed to support the request
|
| 208 |
+
|
| 209 |
+
Respond in valid JSON format.
|
| 210 |
+
""",
|
| 211 |
+
|
| 212 |
+
'contract_analysis': """
|
| 213 |
+
You are an expert contract analyst. Analyze the provided contract documents to answer contract-related queries.
|
| 214 |
+
|
| 215 |
+
USER QUERY: {query}
|
| 216 |
+
|
| 217 |
+
CONTRACT DOCUMENT SECTIONS:
|
| 218 |
+
{context}
|
| 219 |
+
|
| 220 |
+
INSTRUCTIONS:
|
| 221 |
+
1. Read and understand the contract document sections provided
|
| 222 |
+
2. Look for specific terms, conditions, and contractual obligations
|
| 223 |
+
3. Pay attention to deadlines, deliverables, and performance requirements
|
| 224 |
+
4. Consider both what is required AND what is explicitly prohibited
|
| 225 |
+
5. Base your decision ONLY on the information provided in the contract documents
|
| 226 |
+
|
| 227 |
+
ANALYSIS REQUIREMENTS:
|
| 228 |
+
- Decision: PERMITTED, PROHIBITED, CONDITIONAL, or NEEDS_CLARIFICATION
|
| 229 |
+
- Confidence Score: 0.0 to 1.0 (higher if contract clearly states the answer)
|
| 230 |
+
- Justification: Quote specific contract text and explain your reasoning
|
| 231 |
+
- Relevant Clauses: List the exact contract sections that apply
|
| 232 |
+
- Obligations: Any specific contractual obligations or requirements
|
| 233 |
+
- Restrictions: What is explicitly prohibited or limited
|
| 234 |
+
- Remedies: Available remedies or consequences for non-compliance
|
| 235 |
+
|
| 236 |
+
Respond in valid JSON format.
|
| 237 |
+
""",
|
| 238 |
+
|
| 239 |
+
'claim_processing': """
|
| 240 |
+
Analyze the claim processing requirements:
|
| 241 |
+
|
| 242 |
+
Query: {query}
|
| 243 |
+
|
| 244 |
+
Policy Information:
|
| 245 |
+
{context}
|
| 246 |
+
|
| 247 |
+
Please provide:
|
| 248 |
+
1. Decision: APPROVED, DENIED, or PENDING
|
| 249 |
+
2. Confidence Score: 0.0 to 1.0
|
| 250 |
+
3. Justification: Detailed explanation
|
| 251 |
+
4. Required Documents: List of needed documents
|
| 252 |
+
5. Processing Time: Expected processing duration
|
| 253 |
+
6. Steps: Claim processing steps
|
| 254 |
+
7. Relevant Clauses: Policy clauses for claims
|
| 255 |
+
|
| 256 |
+
Respond in JSON format.
|
| 257 |
+
""",
|
| 258 |
+
|
| 259 |
+
'policy_review': """
|
| 260 |
+
Review the policy terms and conditions:
|
| 261 |
+
|
| 262 |
+
Query: {query}
|
| 263 |
+
|
| 264 |
+
Policy Content:
|
| 265 |
+
{context}
|
| 266 |
+
|
| 267 |
+
Please provide:
|
| 268 |
+
1. Decision: CLEAR, UNCLEAR, or NEEDS_CLARIFICATION
|
| 269 |
+
2. Confidence Score: 0.0 to 1.0
|
| 270 |
+
3. Justification: Detailed explanation
|
| 271 |
+
4. Relevant Clauses: Specific policy sections
|
| 272 |
+
5. Key Points: Important policy points
|
| 273 |
+
6. Recommendations: Suggested actions
|
| 274 |
+
|
| 275 |
+
Respond in JSON format.
|
| 276 |
+
"""
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
# Decision mapping for different domains
|
| 280 |
+
self.decision_mapping = {
|
| 281 |
+
# Insurance domain
|
| 282 |
+
'COVERED': 'approved',
|
| 283 |
+
'NOT_COVERED': 'denied',
|
| 284 |
+
'CONDITIONAL': 'pending',
|
| 285 |
+
'APPROVED': 'approved',
|
| 286 |
+
'REJECTED': 'denied',
|
| 287 |
+
'DENIED': 'denied',
|
| 288 |
+
'PENDING': 'pending',
|
| 289 |
+
'PENDING_REVIEW': 'pending',
|
| 290 |
+
|
| 291 |
+
# Legal compliance domain
|
| 292 |
+
'COMPLIANT': 'approved',
|
| 293 |
+
'NON_COMPLIANT': 'denied',
|
| 294 |
+
'NEEDS_REVIEW': 'pending',
|
| 295 |
+
|
| 296 |
+
# Contract domain
|
| 297 |
+
'PERMITTED': 'approved',
|
| 298 |
+
'PROHIBITED': 'denied',
|
| 299 |
+
'NEEDS_CLARIFICATION': 'pending',
|
| 300 |
+
|
| 301 |
+
# Policy review
|
| 302 |
+
'CLEAR': 'approved',
|
| 303 |
+
'UNCLEAR': 'pending'
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
logger.info("Reasoning patterns initialized")
|
| 307 |
+
|
| 308 |
+
except Exception as e:
|
| 309 |
+
logger.error(f"Error initializing reasoning patterns: {e}")
|
| 310 |
+
|
| 311 |
+
def _initialize_clause_extraction(self):
|
| 312 |
+
"""Initialize clause extraction patterns"""
|
| 313 |
+
try:
|
| 314 |
+
# Patterns for extracting policy clauses
|
| 315 |
+
self.clause_patterns = {
|
| 316 |
+
'coverage_clause': [
|
| 317 |
+
r'coverage.*?shall.*?include',
|
| 318 |
+
r'covered.*?expenses.*?include',
|
| 319 |
+
r'benefits.*?shall.*?cover',
|
| 320 |
+
r'policy.*?covers.*?following'
|
| 321 |
+
],
|
| 322 |
+
'exclusion_clause': [
|
| 323 |
+
r'exclusions.*?include',
|
| 324 |
+
r'not.*?covered.*?following',
|
| 325 |
+
r'excluded.*?from.*?coverage',
|
| 326 |
+
r'coverage.*?does.*?not.*?include'
|
| 327 |
+
],
|
| 328 |
+
'condition_clause': [
|
| 329 |
+
r'conditions.*?precedent',
|
| 330 |
+
r'requirements.*?for.*?coverage',
|
| 331 |
+
r'must.*?meet.*?following',
|
| 332 |
+
r'coverage.*?subject.*?to'
|
| 333 |
+
],
|
| 334 |
+
'amount_clause': [
|
| 335 |
+
r'maximum.*?benefit.*?\$[\d,]+',
|
| 336 |
+
r'coverage.*?limit.*?\$[\d,]+',
|
| 337 |
+
r'benefit.*?amount.*?\$[\d,]+',
|
| 338 |
+
r'up.*?to.*?\$[\d,]+'
|
| 339 |
+
],
|
| 340 |
+
'waiting_period': [
|
| 341 |
+
r'waiting.*?period.*?\d+.*?(days?|weeks?|months?)',
|
| 342 |
+
r'coverage.*?begins.*?after.*?\d+',
|
| 343 |
+
r'benefits.*?available.*?after.*?\d+'
|
| 344 |
+
]
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
logger.info("Clause extraction patterns initialized")
|
| 348 |
+
|
| 349 |
+
except Exception as e:
|
| 350 |
+
logger.error(f"Error initializing clause extraction: {e}")
|
| 351 |
+
|
| 352 |
+
def analyze_query(self,
|
| 353 |
+
query: str,
|
| 354 |
+
context: List[Dict[str, Any]],
|
| 355 |
+
query_type: str = 'coverage_check') -> ReasoningResult:
|
| 356 |
+
"""Main method to analyze a query using LLM reasoning with detailed analysis"""
|
| 357 |
+
try:
|
| 358 |
+
# Prepare context from relevant chunks
|
| 359 |
+
formatted_context = self._format_context_for_llm(context)
|
| 360 |
+
|
| 361 |
+
# Create comprehensive reasoning prompt
|
| 362 |
+
prompt = f"""
|
| 363 |
+
You are an expert insurance policy analyzer. Analyze the following query against the provided policy documents.
|
| 364 |
+
|
| 365 |
+
User Query: {query}
|
| 366 |
+
|
| 367 |
+
Relevant Policy Clauses:
|
| 368 |
+
{formatted_context}
|
| 369 |
+
|
| 370 |
+
Please provide a structured analysis in the following JSON format:
|
| 371 |
+
{{
|
| 372 |
+
"decision": "approved/rejected/conditional",
|
| 373 |
+
"amount": <amount if applicable, null otherwise>,
|
| 374 |
+
"justification": "<detailed explanation with specific clause references>",
|
| 375 |
+
"relevant_clauses": ["<list of clause IDs that support the decision>"],
|
| 376 |
+
"confidence_score": <0.0 to 1.0>,
|
| 377 |
+
"conditions": ["<any conditions that must be met>"],
|
| 378 |
+
"exclusions": ["<what is explicitly excluded>"],
|
| 379 |
+
"waiting_period": "<waiting period if applicable>",
|
| 380 |
+
"required_documents": ["<documents needed for claim>"],
|
| 381 |
+
"reasoning_steps": ["<step-by-step reasoning process>"]
|
| 382 |
+
}}
|
| 383 |
+
|
| 384 |
+
Base your decision on:
|
| 385 |
+
1. Policy coverage and exclusions
|
| 386 |
+
2. Eligibility criteria
|
| 387 |
+
3. Waiting periods
|
| 388 |
+
4. Pre-existing conditions
|
| 389 |
+
5. Specific terms and conditions
|
| 390 |
+
6. Time limitations and restrictions
|
| 391 |
+
7. Required documentation
|
| 392 |
+
|
| 393 |
+
IMPORTANT:
|
| 394 |
+
- Quote specific policy text in your justification
|
| 395 |
+
- Reference exact clause IDs from the provided context
|
| 396 |
+
- If the policy explicitly states limitations, reflect them accurately
|
| 397 |
+
- Provide detailed reasoning, not just yes/no answers
|
| 398 |
+
- Consider both what is covered AND what is excluded
|
| 399 |
+
- Look for specific amounts, time periods, and conditions
|
| 400 |
+
|
| 401 |
+
JSON Response:
|
| 402 |
+
"""
|
| 403 |
+
|
| 404 |
+
# Generate response using LLM
|
| 405 |
+
response = self._generate_llm_response(prompt)
|
| 406 |
+
|
| 407 |
+
# Parse the response
|
| 408 |
+
parsed_result = self._parse_llm_response(response, query_type)
|
| 409 |
+
|
| 410 |
+
# Extract clause references
|
| 411 |
+
clause_references = self._extract_clause_references(context, parsed_result)
|
| 412 |
+
|
| 413 |
+
# Build final result with comprehensive analysis
|
| 414 |
+
result = ReasoningResult(
|
| 415 |
+
decision=parsed_result.get('decision', 'pending'),
|
| 416 |
+
confidence_score=parsed_result.get('confidence_score', 0.5),
|
| 417 |
+
justification=parsed_result.get('justification', 'Unable to determine'),
|
| 418 |
+
relevant_clauses=parsed_result.get('relevant_clauses', []),
|
| 419 |
+
amount=parsed_result.get('amount'),
|
| 420 |
+
waiting_period=parsed_result.get('waiting_period'),
|
| 421 |
+
conditions=parsed_result.get('conditions', []),
|
| 422 |
+
exclusions=parsed_result.get('exclusions', []),
|
| 423 |
+
required_documents=parsed_result.get('required_documents', []),
|
| 424 |
+
processing_time=parsed_result.get('processing_time'),
|
| 425 |
+
reasoning_steps=parsed_result.get('reasoning_steps', []),
|
| 426 |
+
source_references=clause_references
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
logger.info(f"Query analysis completed: {result.decision} ({result.confidence_score:.2f})")
|
| 430 |
+
return result
|
| 431 |
+
|
| 432 |
+
except Exception as e:
|
| 433 |
+
logger.error(f"Error analyzing query: {e}")
|
| 434 |
+
return self._create_fallback_result(query)
|
| 435 |
+
|
| 436 |
+
def _format_context_for_llm(self, context: List[Dict[str, Any]]) -> str:
|
| 437 |
+
"""Format context for LLM consumption"""
|
| 438 |
+
try:
|
| 439 |
+
formatted_parts = []
|
| 440 |
+
|
| 441 |
+
for i, item in enumerate(context, 1):
|
| 442 |
+
content = item.get('content', '')
|
| 443 |
+
source = item.get('source_file', 'Unknown')
|
| 444 |
+
similarity = item.get('similarity_score', 0.0)
|
| 445 |
+
|
| 446 |
+
formatted_parts.append(f"Section {i} (Source: {source}, Relevance: {similarity:.2f}):\n{content}\n")
|
| 447 |
+
|
| 448 |
+
return "\n".join(formatted_parts)
|
| 449 |
+
|
| 450 |
+
except Exception as e:
|
| 451 |
+
logger.error(f"Error formatting context: {e}")
|
| 452 |
+
return str(context)
|
| 453 |
+
|
| 454 |
+
def _generate_llm_response(self, prompt: str) -> str:
|
| 455 |
+
"""Generate response using the LLM"""
|
| 456 |
+
try:
|
| 457 |
+
# Check if LLM is available
|
| 458 |
+
if self.llm is None:
|
| 459 |
+
logger.warning("LLM not available, using fallback response")
|
| 460 |
+
return self._generate_fallback_response(prompt)
|
| 461 |
+
|
| 462 |
+
# Create system prompt
|
| 463 |
+
system_prompt = """You are an expert insurance policy analyzer. Your job is to:
|
| 464 |
+
1. Carefully read and understand the policy document sections provided
|
| 465 |
+
2. Answer the user's query based ONLY on the information in the policy document
|
| 466 |
+
3. Look for specific clauses, conditions, limitations, and exclusions
|
| 467 |
+
4. Pay attention to time periods, coverage limits, and restrictions
|
| 468 |
+
5. If the policy explicitly states something is NOT covered, you must say it's REJECTED
|
| 469 |
+
6. If there are specific conditions or limitations, you must mention them
|
| 470 |
+
7. Always respond in valid JSON format with accurate information
|
| 471 |
+
8. Quote specific policy text in your justification
|
| 472 |
+
9. Reference exact clause IDs from the provided context
|
| 473 |
+
10. Provide detailed reasoning, not just yes/no answers
|
| 474 |
+
11. Consider both what is covered AND what is excluded
|
| 475 |
+
12. Look for specific amounts, time periods, and conditions
|
| 476 |
+
13. Do not make assumptions - base your decision only on what the policy document states"""
|
| 477 |
+
|
| 478 |
+
# Generate response
|
| 479 |
+
response = self.llm.create_completion(
|
| 480 |
+
prompt=f"{system_prompt}\n\n{prompt}",
|
| 481 |
+
max_tokens=self.max_tokens,
|
| 482 |
+
temperature=0.1,
|
| 483 |
+
stop=["```", "Human:", "Assistant:"]
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
return response['choices'][0]['text'].strip()
|
| 487 |
+
|
| 488 |
+
except Exception as e:
|
| 489 |
+
logger.error(f"Error generating LLM response: {e}")
|
| 490 |
+
return self._generate_fallback_response(prompt)
|
| 491 |
+
|
| 492 |
+
def _parse_llm_response(self, response: str, query_type: str) -> Dict[str, Any]:
|
| 493 |
+
"""Parse the LLM response into structured data"""
|
| 494 |
+
try:
|
| 495 |
+
# Try to extract JSON from response
|
| 496 |
+
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
| 497 |
+
if json_match:
|
| 498 |
+
json_str = json_match.group(0)
|
| 499 |
+
parsed = json.loads(json_str)
|
| 500 |
+
else:
|
| 501 |
+
# Fallback parsing
|
| 502 |
+
parsed = self._fallback_parse_response(response)
|
| 503 |
+
|
| 504 |
+
# Map decision to standard format
|
| 505 |
+
if 'decision' in parsed:
|
| 506 |
+
parsed['decision'] = self.decision_mapping.get(
|
| 507 |
+
parsed['decision'].upper(), 'pending'
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
# Ensure confidence score is float
|
| 511 |
+
if 'confidence_score' in parsed:
|
| 512 |
+
try:
|
| 513 |
+
parsed['confidence_score'] = float(parsed['confidence_score'])
|
| 514 |
+
except:
|
| 515 |
+
parsed['confidence_score'] = 0.5
|
| 516 |
+
|
| 517 |
+
return parsed
|
| 518 |
+
|
| 519 |
+
except Exception as e:
|
| 520 |
+
logger.error(f"Error parsing LLM response: {e}")
|
| 521 |
+
return {
|
| 522 |
+
'decision': 'pending',
|
| 523 |
+
'confidence_score': 0.5,
|
| 524 |
+
'justification': 'Unable to parse response',
|
| 525 |
+
'relevant_clauses': []
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
def _fallback_parse_response(self, response: str) -> Dict[str, Any]:
|
| 529 |
+
"""Fallback parsing when JSON extraction fails"""
|
| 530 |
+
try:
|
| 531 |
+
result = {
|
| 532 |
+
'decision': 'pending',
|
| 533 |
+
'confidence_score': 0.5,
|
| 534 |
+
'justification': response[:500],
|
| 535 |
+
'relevant_clauses': []
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
# Try to extract decision
|
| 539 |
+
if 'covered' in response.lower():
|
| 540 |
+
result['decision'] = 'approved'
|
| 541 |
+
elif 'not covered' in response.lower() or 'excluded' in response.lower():
|
| 542 |
+
result['decision'] = 'denied'
|
| 543 |
+
|
| 544 |
+
# Try to extract confidence
|
| 545 |
+
confidence_match = re.search(r'confidence.*?(\d+\.?\d*)', response, re.IGNORECASE)
|
| 546 |
+
if confidence_match:
|
| 547 |
+
try:
|
| 548 |
+
result['confidence_score'] = float(confidence_match.group(1))
|
| 549 |
+
except:
|
| 550 |
+
pass
|
| 551 |
+
|
| 552 |
+
return result
|
| 553 |
+
|
| 554 |
+
except Exception as e:
|
| 555 |
+
logger.error(f"Error in fallback parsing: {e}")
|
| 556 |
+
return {
|
| 557 |
+
'decision': 'pending',
|
| 558 |
+
'confidence_score': 0.5,
|
| 559 |
+
'justification': 'Analysis failed',
|
| 560 |
+
'relevant_clauses': []
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
def _extract_clause_references(self,
|
| 564 |
+
context: List[Dict[str, Any]],
|
| 565 |
+
parsed_result: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 566 |
+
"""Extract specific clause references from context"""
|
| 567 |
+
try:
|
| 568 |
+
references = []
|
| 569 |
+
|
| 570 |
+
for item in context:
|
| 571 |
+
content = item.get('content', '')
|
| 572 |
+
source = item.get('source_file', 'Unknown')
|
| 573 |
+
|
| 574 |
+
# Extract clauses using patterns
|
| 575 |
+
for clause_type, patterns in self.clause_patterns.items():
|
| 576 |
+
for pattern in patterns:
|
| 577 |
+
matches = re.findall(pattern, content, re.IGNORECASE)
|
| 578 |
+
for match in matches:
|
| 579 |
+
references.append({
|
| 580 |
+
'clause_type': clause_type,
|
| 581 |
+
'clause_text': match,
|
| 582 |
+
'source_file': source,
|
| 583 |
+
'relevance_score': item.get('similarity_score', 0.0)
|
| 584 |
+
})
|
| 585 |
+
|
| 586 |
+
return references
|
| 587 |
+
|
| 588 |
+
except Exception as e:
|
| 589 |
+
logger.error(f"Error extracting clause references: {e}")
|
| 590 |
+
return []
|
| 591 |
+
|
| 592 |
+
def _generate_fallback_response(self, prompt: str) -> str:
|
| 593 |
+
"""Generate a fallback response when LLM is not available"""
|
| 594 |
+
try:
|
| 595 |
+
# Extract query and context from prompt
|
| 596 |
+
query_match = re.search(r'USER QUERY:\s*(.+?)(?=\n\n|$)', prompt, re.DOTALL | re.IGNORECASE)
|
| 597 |
+
context_match = re.search(r'POLICY DOCUMENT SECTIONS:\s*(.+?)(?=\n\n|$)', prompt, re.DOTALL | re.IGNORECASE)
|
| 598 |
+
|
| 599 |
+
query = query_match.group(1).strip() if query_match else "Unknown query"
|
| 600 |
+
context = context_match.group(1).strip() if context_match else "No policy context provided"
|
| 601 |
+
|
| 602 |
+
# Analyze the context for key information
|
| 603 |
+
context_lower = context.lower()
|
| 604 |
+
query_lower = query.lower()
|
| 605 |
+
|
| 606 |
+
# Look for specific policy terms and conditions
|
| 607 |
+
decision = "PENDING"
|
| 608 |
+
confidence = 0.5
|
| 609 |
+
justification = "Unable to determine coverage without proper policy analysis"
|
| 610 |
+
relevant_clauses = []
|
| 611 |
+
conditions = []
|
| 612 |
+
exclusions = []
|
| 613 |
+
|
| 614 |
+
# Check for coverage limitations and restrictions
|
| 615 |
+
if any(term in context_lower for term in ['until first discharge', 'discharge from hospital', 'hospitalization period', 'limited to']):
|
| 616 |
+
if any(term in query_lower for term in ['after discharge', 'post discharge', 'discharge', 'beyond']):
|
| 617 |
+
decision = "REJECTED"
|
| 618 |
+
confidence = 0.9
|
| 619 |
+
justification = "Policy explicitly states coverage is limited to hospitalization period until first discharge. Post-discharge care is not covered under this policy."
|
| 620 |
+
relevant_clauses = ["newborn coverage", "discharge limitation", "hospitalization period"]
|
| 621 |
+
conditions = ["Coverage only during hospitalization", "Until first discharge"]
|
| 622 |
+
exclusions = ["Post-discharge care", "Outpatient newborn care"]
|
| 623 |
+
waiting_period = "Until first discharge"
|
| 624 |
+
required_documents = ["Hospital discharge summary", "Birth certificate"]
|
| 625 |
+
|
| 626 |
+
# Check for waiting periods
|
| 627 |
+
elif any(term in context_lower for term in ['waiting period', 'waiting periods', 'time requirement']):
|
| 628 |
+
decision = "CONDITIONAL"
|
| 629 |
+
confidence = 0.7
|
| 630 |
+
justification = "Coverage subject to waiting period requirements as specified in the policy"
|
| 631 |
+
relevant_clauses = ["waiting periods", "time requirements"]
|
| 632 |
+
conditions = ["Waiting period must be satisfied"]
|
| 633 |
+
waiting_period = "As specified in policy"
|
| 634 |
+
|
| 635 |
+
# Check for exclusions
|
| 636 |
+
elif any(term in context_lower for term in ['not covered', 'excluded', 'exclusions', 'prohibited', 'not permitted']):
|
| 637 |
+
decision = "REJECTED"
|
| 638 |
+
confidence = 0.8
|
| 639 |
+
justification = "Policy explicitly excludes or prohibits this type of coverage"
|
| 640 |
+
relevant_clauses = ["exclusions", "prohibitions"]
|
| 641 |
+
exclusions = ["Excluded per policy terms"]
|
| 642 |
+
|
| 643 |
+
# Check for covered items
|
| 644 |
+
elif any(term in context_lower for term in ['covered', 'coverage', 'benefits', 'permitted', 'allowed']):
|
| 645 |
+
decision = "APPROVED"
|
| 646 |
+
confidence = 0.7
|
| 647 |
+
justification = "Policy indicates this type of coverage is permitted"
|
| 648 |
+
relevant_clauses = ["coverage", "benefits"]
|
| 649 |
+
conditions = ["Subject to policy terms"]
|
| 650 |
+
|
| 651 |
+
# Default case - analyze based on context content
|
| 652 |
+
else:
|
| 653 |
+
# Look for positive indicators in context
|
| 654 |
+
if any(term in context_lower for term in ['covered', 'coverage', 'benefits', 'permitted']):
|
| 655 |
+
decision = "APPROVED"
|
| 656 |
+
confidence = 0.6
|
| 657 |
+
justification = "Policy appears to provide coverage for this type of request"
|
| 658 |
+
relevant_clauses = ["general coverage"]
|
| 659 |
+
conditions = ["Subject to policy terms"]
|
| 660 |
+
elif any(term in context_lower for term in ['excluded', 'not covered', 'prohibited']):
|
| 661 |
+
decision = "REJECTED"
|
| 662 |
+
confidence = 0.6
|
| 663 |
+
justification = "Policy appears to exclude this type of coverage"
|
| 664 |
+
relevant_clauses = ["exclusions"]
|
| 665 |
+
exclusions = ["Excluded per policy terms"]
|
| 666 |
+
else:
|
| 667 |
+
# Try to extract any relevant information from the context
|
| 668 |
+
if len(context) > 0:
|
| 669 |
+
decision = "CONDITIONAL"
|
| 670 |
+
confidence = 0.5
|
| 671 |
+
justification = f"Based on the available policy information, this request requires further review. Found {len(context)} relevant policy sections."
|
| 672 |
+
relevant_clauses = ["policy sections found"]
|
| 673 |
+
conditions = ["Policy review required", "Additional documentation may be needed"]
|
| 674 |
+
else:
|
| 675 |
+
decision = "PENDING"
|
| 676 |
+
confidence = 0.3
|
| 677 |
+
justification = "No relevant policy information found. Please ensure the policy document has been properly uploaded and processed."
|
| 678 |
+
relevant_clauses = ["no policy data"]
|
| 679 |
+
conditions = ["Policy document upload required"]
|
| 680 |
+
|
| 681 |
+
# Check for specific amounts in context
|
| 682 |
+
amount_match = re.search(r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:rs?|rupees?|inr|\$)', context_lower)
|
| 683 |
+
if amount_match:
|
| 684 |
+
amount = float(amount_match.group(1).replace(',', ''))
|
| 685 |
+
else:
|
| 686 |
+
amount = None
|
| 687 |
+
|
| 688 |
+
return json.dumps({
|
| 689 |
+
"decision": decision,
|
| 690 |
+
"confidence_score": confidence,
|
| 691 |
+
"justification": justification,
|
| 692 |
+
"relevant_clauses": relevant_clauses,
|
| 693 |
+
"amount": amount,
|
| 694 |
+
"waiting_period": waiting_period,
|
| 695 |
+
"conditions": conditions,
|
| 696 |
+
"exclusions": exclusions,
|
| 697 |
+
"required_documents": ["Policy document", "Claim form"]
|
| 698 |
+
})
|
| 699 |
+
|
| 700 |
+
except Exception as e:
|
| 701 |
+
logger.error(f"Error generating fallback response: {e}")
|
| 702 |
+
return json.dumps({
|
| 703 |
+
"decision": "PENDING",
|
| 704 |
+
"confidence_score": 0.5,
|
| 705 |
+
"justification": "Unable to analyze policy information",
|
| 706 |
+
"relevant_clauses": [],
|
| 707 |
+
"conditions": [],
|
| 708 |
+
"exclusions": []
|
| 709 |
+
})
|
| 710 |
+
|
| 711 |
+
def _create_fallback_result(self, query: str) -> ReasoningResult:
|
| 712 |
+
"""Create a fallback result when analysis fails"""
|
| 713 |
+
return ReasoningResult(
|
| 714 |
+
decision='pending',
|
| 715 |
+
confidence_score=0.0,
|
| 716 |
+
justification='Unable to analyze query due to technical issues',
|
| 717 |
+
relevant_clauses=[],
|
| 718 |
+
reasoning_steps=['Analysis failed'],
|
| 719 |
+
source_references=[]
|
| 720 |
+
)
|
| 721 |
+
|
| 722 |
+
def explain_decision(self, result: ReasoningResult) -> str:
|
| 723 |
+
"""Generate a human-readable explanation of the decision"""
|
| 724 |
+
try:
|
| 725 |
+
explanation_parts = []
|
| 726 |
+
|
| 727 |
+
# Main decision
|
| 728 |
+
explanation_parts.append(f"Decision: {result.decision.upper()}")
|
| 729 |
+
explanation_parts.append(f"Confidence: {result.confidence_score:.1%}")
|
| 730 |
+
|
| 731 |
+
# Justification
|
| 732 |
+
if result.justification:
|
| 733 |
+
explanation_parts.append(f"\nJustification:\n{result.justification}")
|
| 734 |
+
|
| 735 |
+
# Relevant clauses
|
| 736 |
+
if result.relevant_clauses:
|
| 737 |
+
explanation_parts.append(f"\nRelevant Policy Clauses:")
|
| 738 |
+
for clause in result.relevant_clauses:
|
| 739 |
+
explanation_parts.append(f"- {clause}")
|
| 740 |
+
|
| 741 |
+
# Amount information
|
| 742 |
+
if result.amount:
|
| 743 |
+
explanation_parts.append(f"\nCoverage Amount: ${result.amount:,.2f}")
|
| 744 |
+
|
| 745 |
+
# Waiting period
|
| 746 |
+
if result.waiting_period:
|
| 747 |
+
explanation_parts.append(f"\nWaiting Period: {result.waiting_period}")
|
| 748 |
+
|
| 749 |
+
# Conditions
|
| 750 |
+
if result.conditions:
|
| 751 |
+
explanation_parts.append(f"\nConditions:")
|
| 752 |
+
for condition in result.conditions:
|
| 753 |
+
explanation_parts.append(f"- {condition}")
|
| 754 |
+
|
| 755 |
+
# Exclusions
|
| 756 |
+
if result.exclusions:
|
| 757 |
+
explanation_parts.append(f"\nExclusions:")
|
| 758 |
+
for exclusion in result.exclusions:
|
| 759 |
+
explanation_parts.append(f"- {exclusion}")
|
| 760 |
+
|
| 761 |
+
# Required documents
|
| 762 |
+
if result.required_documents:
|
| 763 |
+
explanation_parts.append(f"\nRequired Documents:")
|
| 764 |
+
for doc in result.required_documents:
|
| 765 |
+
explanation_parts.append(f"- {doc}")
|
| 766 |
+
|
| 767 |
+
return "\n".join(explanation_parts)
|
| 768 |
+
|
| 769 |
+
except Exception as e:
|
| 770 |
+
logger.error(f"Error explaining decision: {e}")
|
| 771 |
+
return f"Decision: {result.decision.upper()}\nConfidence: {result.confidence_score:.1%}\nJustification: {result.justification}"
|
| 772 |
+
|
| 773 |
+
def validate_decision(self, result: ReasoningResult) -> bool:
|
| 774 |
+
"""Validate the reasoning result for consistency"""
|
| 775 |
+
try:
|
| 776 |
+
# Check if confidence score is valid
|
| 777 |
+
if not (0.0 <= result.confidence_score <= 1.0):
|
| 778 |
+
return False
|
| 779 |
+
|
| 780 |
+
# Check if decision is valid
|
| 781 |
+
valid_decisions = ['approved', 'denied', 'pending']
|
| 782 |
+
if result.decision not in valid_decisions:
|
| 783 |
+
return False
|
| 784 |
+
|
| 785 |
+
# Check if justification is provided
|
| 786 |
+
if not result.justification or len(result.justification.strip()) < 10:
|
| 787 |
+
return False
|
| 788 |
+
|
| 789 |
+
return True
|
| 790 |
+
|
| 791 |
+
except Exception as e:
|
| 792 |
+
logger.error(f"Error validating decision: {e}")
|
| 793 |
+
return False
|
| 794 |
+
|
| 795 |
+
# Example usage
|
| 796 |
+
if __name__ == "__main__":
|
| 797 |
+
# Initialize reasoning engine
|
| 798 |
+
reasoning_engine = AdvancedLLMReasoning()
|
| 799 |
+
|
| 800 |
+
# Test query
|
| 801 |
+
test_query = "Is heart surgery covered under this policy?"
|
| 802 |
+
test_context = [
|
| 803 |
+
{
|
| 804 |
+
'content': 'This policy covers medical procedures including heart surgery up to $50,000.',
|
| 805 |
+
'source_file': 'policy.pdf',
|
| 806 |
+
'similarity_score': 0.9
|
| 807 |
+
}
|
| 808 |
+
]
|
| 809 |
+
|
| 810 |
+
# Analyze query
|
| 811 |
+
result = reasoning_engine.analyze_query(test_query, test_context, 'coverage_check')
|
| 812 |
+
|
| 813 |
+
print(f"Decision: {result.decision}")
|
| 814 |
+
print(f"Confidence: {result.confidence_score:.2f}")
|
| 815 |
+
print(f"Justification: {result.justification}")
|
| 816 |
+
|
| 817 |
+
# Explain decision
|
| 818 |
+
explanation = reasoning_engine.explain_decision(result)
|
| 819 |
+
print(f"\nExplanation:\n{explanation}")
|
main.py
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main Entry Point for Advanced RAG System
|
| 3 |
+
Provides user-friendly interface for document upload and query processing
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
import argparse
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import List, Dict, Any, Optional
|
| 13 |
+
|
| 14 |
+
# Import our RAG system
|
| 15 |
+
from rag_system import AdvancedRAGSystem, QueryResult
|
| 16 |
+
|
| 17 |
+
# Import document processor for direct access
|
| 18 |
+
from document_processer import AdvancedDocumentProcessor
|
| 19 |
+
|
| 20 |
+
# Import pytesseract for direct OCR
|
| 21 |
+
try:
|
| 22 |
+
import pytesseract
|
| 23 |
+
from PIL import Image
|
| 24 |
+
import cv2
|
| 25 |
+
import numpy as np
|
| 26 |
+
OCR_AVAILABLE = True
|
| 27 |
+
except ImportError:
|
| 28 |
+
OCR_AVAILABLE = False
|
| 29 |
+
|
| 30 |
+
def print_banner():
|
| 31 |
+
"""Print system banner"""
|
| 32 |
+
print("=" * 80)
|
| 33 |
+
print("🚀 ADVANCED RAG SYSTEM - INSURANCE POLICY ANALYZER")
|
| 34 |
+
print("=" * 80)
|
| 35 |
+
print("📋 Features:")
|
| 36 |
+
print(" • Multi-format document processing (PDF, TXT, Email, etc.)")
|
| 37 |
+
print(" • Advanced OCR for scanned documents")
|
| 38 |
+
print(" • Natural language query processing")
|
| 39 |
+
print(" • Clause referencing and explanation")
|
| 40 |
+
print(" • Comprehensive audit trail")
|
| 41 |
+
print(" • GPU-accelerated processing")
|
| 42 |
+
print("=" * 80)
|
| 43 |
+
|
| 44 |
+
def print_menu():
|
| 45 |
+
"""Print main menu options"""
|
| 46 |
+
print("\n📋 MAIN MENU:")
|
| 47 |
+
print("1. 📄 Upload Document")
|
| 48 |
+
print("2. 🤔 Process Query")
|
| 49 |
+
print("3. 📊 System Statistics")
|
| 50 |
+
print("4. 🔍 View Audit Trail")
|
| 51 |
+
print("5. 🧪 System Validation")
|
| 52 |
+
print("6. 💾 Export System Data")
|
| 53 |
+
print("7. 🗑️ Clear System")
|
| 54 |
+
print("8. 🧪 Test OCR")
|
| 55 |
+
print("9. ❓ Help")
|
| 56 |
+
print("0. 🚪 Exit")
|
| 57 |
+
print("-" * 40)
|
| 58 |
+
|
| 59 |
+
def get_user_choice() -> str:
|
| 60 |
+
"""Get user choice from menu"""
|
| 61 |
+
try:
|
| 62 |
+
choice = input("\n🎯 Enter your choice (1-9): ").strip()
|
| 63 |
+
return choice
|
| 64 |
+
except KeyboardInterrupt:
|
| 65 |
+
print("\n👋 Goodbye!")
|
| 66 |
+
sys.exit(0)
|
| 67 |
+
|
| 68 |
+
def simple_ocr_pdf(file_path: str) -> str:
|
| 69 |
+
"""Simple OCR function using pytesseract directly"""
|
| 70 |
+
if not OCR_AVAILABLE:
|
| 71 |
+
print("❌ pytesseract not available. Please install it with: pip install pytesseract")
|
| 72 |
+
return ""
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
import fitz # PyMuPDF
|
| 76 |
+
|
| 77 |
+
# Open PDF
|
| 78 |
+
doc = fitz.open(file_path)
|
| 79 |
+
all_text = ""
|
| 80 |
+
|
| 81 |
+
for page_num in range(len(doc)):
|
| 82 |
+
page = doc.load_page(page_num)
|
| 83 |
+
|
| 84 |
+
# Get page as image
|
| 85 |
+
pix = page.get_pixmap()
|
| 86 |
+
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
| 87 |
+
|
| 88 |
+
# Extract text using OCR
|
| 89 |
+
text = pytesseract.image_to_string(img, lang='eng', config='--psm 6')
|
| 90 |
+
all_text += f"\n\n--- Page {page_num + 1} ---\n{text}"
|
| 91 |
+
|
| 92 |
+
doc.close()
|
| 93 |
+
return all_text
|
| 94 |
+
|
| 95 |
+
except Exception as e:
|
| 96 |
+
print(f"❌ OCR error: {e}")
|
| 97 |
+
return ""
|
| 98 |
+
|
| 99 |
+
def upload_document(rag_system: AdvancedRAGSystem):
|
| 100 |
+
"""Handle document upload"""
|
| 101 |
+
print("\n📄 DOCUMENT UPLOAD")
|
| 102 |
+
print("-" * 40)
|
| 103 |
+
|
| 104 |
+
try:
|
| 105 |
+
# Get file path
|
| 106 |
+
file_path = input("📁 Enter file path (or 'browse' for file dialog): ").strip()
|
| 107 |
+
|
| 108 |
+
if file_path.lower() == 'browse':
|
| 109 |
+
try:
|
| 110 |
+
import tkinter as tk
|
| 111 |
+
from tkinter import filedialog
|
| 112 |
+
|
| 113 |
+
root = tk.Tk()
|
| 114 |
+
root.withdraw()
|
| 115 |
+
|
| 116 |
+
file_path = filedialog.askopenfilename(
|
| 117 |
+
title="Select Document",
|
| 118 |
+
filetypes=[
|
| 119 |
+
("All supported", "*.pdf;*.txt;*.docx;*.html;*.eml;*.csv;*.json"),
|
| 120 |
+
("PDF files", "*.pdf"),
|
| 121 |
+
("Text files", "*.txt"),
|
| 122 |
+
("Word documents", "*.docx"),
|
| 123 |
+
("HTML files", "*.html"),
|
| 124 |
+
("Email files", "*.eml"),
|
| 125 |
+
("CSV files", "*.csv"),
|
| 126 |
+
("JSON files", "*.json"),
|
| 127 |
+
("All files", "*.*")
|
| 128 |
+
]
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
root.destroy()
|
| 132 |
+
|
| 133 |
+
if not file_path:
|
| 134 |
+
print("❌ No file selected.")
|
| 135 |
+
return
|
| 136 |
+
|
| 137 |
+
except ImportError:
|
| 138 |
+
print("❌ File browser not available. Please enter the file path manually.")
|
| 139 |
+
return
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"❌ Error opening file browser: {e}")
|
| 142 |
+
return
|
| 143 |
+
|
| 144 |
+
# Check if file exists
|
| 145 |
+
if not os.path.exists(file_path):
|
| 146 |
+
print(f"❌ File not found: {file_path}")
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
# Check file type
|
| 150 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 151 |
+
file_extension = Path(file_path).suffix.lower()
|
| 152 |
+
|
| 153 |
+
if file_extension not in supported_extensions:
|
| 154 |
+
print(f"❌ Unsupported file type: {file_extension}")
|
| 155 |
+
print(f"💡 Supported types: {', '.join(supported_extensions)}")
|
| 156 |
+
return
|
| 157 |
+
|
| 158 |
+
# Ask about OCR for PDF files
|
| 159 |
+
use_ocr = False
|
| 160 |
+
if file_extension == '.pdf':
|
| 161 |
+
if OCR_AVAILABLE:
|
| 162 |
+
ocr_choice = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
|
| 163 |
+
use_ocr = ocr_choice in ['y', 'yes']
|
| 164 |
+
else:
|
| 165 |
+
print("⚠️ OCR not available. Processing without OCR.")
|
| 166 |
+
|
| 167 |
+
print(f"\n⏳ Processing document: {os.path.basename(file_path)}")
|
| 168 |
+
print("💡 This may take a few moments...")
|
| 169 |
+
|
| 170 |
+
# Process document
|
| 171 |
+
start_time = time.time()
|
| 172 |
+
try:
|
| 173 |
+
chunks = rag_system.ingest_document(file_path, use_ocr=use_ocr)
|
| 174 |
+
processing_time = time.time() - start_time
|
| 175 |
+
except Exception as e:
|
| 176 |
+
print(f"❌ Document ingestion failed: {e}")
|
| 177 |
+
print("💡 Trying alternative processing method...")
|
| 178 |
+
|
| 179 |
+
# Fallback: Use document processor directly
|
| 180 |
+
try:
|
| 181 |
+
doc_processor = AdvancedDocumentProcessor()
|
| 182 |
+
chunks = doc_processor.process_document(file_path, use_ocr=use_ocr)
|
| 183 |
+
|
| 184 |
+
# Add chunks to vector database manually
|
| 185 |
+
if chunks:
|
| 186 |
+
success = rag_system.vector_database.add_documents(chunks)
|
| 187 |
+
if success:
|
| 188 |
+
print("✅ Document processed using fallback method")
|
| 189 |
+
processing_time = time.time() - start_time
|
| 190 |
+
else:
|
| 191 |
+
print("❌ Failed to add documents to vector database")
|
| 192 |
+
chunks = []
|
| 193 |
+
else:
|
| 194 |
+
print("❌ No chunks generated from document")
|
| 195 |
+
chunks = []
|
| 196 |
+
processing_time = time.time() - start_time
|
| 197 |
+
except Exception as fallback_error:
|
| 198 |
+
print(f"❌ Fallback processing also failed: {fallback_error}")
|
| 199 |
+
chunks = []
|
| 200 |
+
processing_time = time.time() - start_time
|
| 201 |
+
|
| 202 |
+
if chunks:
|
| 203 |
+
print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
|
| 204 |
+
print(f"📊 Document chunks created:")
|
| 205 |
+
|
| 206 |
+
for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
|
| 207 |
+
print(f" Chunk {i+1}: {chunk.chunk_id}")
|
| 208 |
+
print(f" Type: {chunk.section_type}")
|
| 209 |
+
print(f" Content preview: {chunk.content[:100]}...")
|
| 210 |
+
print()
|
| 211 |
+
|
| 212 |
+
if len(chunks) > 5:
|
| 213 |
+
print(f" ... and {len(chunks) - 5} more chunks")
|
| 214 |
+
else:
|
| 215 |
+
print("❌ Failed to process document")
|
| 216 |
+
if file_extension == '.pdf' and not use_ocr:
|
| 217 |
+
print("💡 Try using OCR for scanned PDFs: Select 'y' when asked about OCR")
|
| 218 |
+
|
| 219 |
+
except KeyboardInterrupt:
|
| 220 |
+
print("\n👋 Upload cancelled.")
|
| 221 |
+
except Exception as e:
|
| 222 |
+
print(f"❌ Error uploading document: {e}")
|
| 223 |
+
|
| 224 |
+
def process_query(rag_system: AdvancedRAGSystem):
|
| 225 |
+
"""Interactive query processing with continuous questioning"""
|
| 226 |
+
print("\n🤔 INTERACTIVE QUERY PROCESSING")
|
| 227 |
+
print("-" * 40)
|
| 228 |
+
|
| 229 |
+
print("💡 Enter your questions about the uploaded documents.")
|
| 230 |
+
print("💡 Type 'quit' or 'exit' to stop asking questions.")
|
| 231 |
+
print("💡 Type 'help' for example questions.")
|
| 232 |
+
print()
|
| 233 |
+
|
| 234 |
+
results = []
|
| 235 |
+
query_count = 0
|
| 236 |
+
|
| 237 |
+
while True:
|
| 238 |
+
try:
|
| 239 |
+
# Get user input
|
| 240 |
+
user_query = input("🤔 Enter your question: ").strip()
|
| 241 |
+
|
| 242 |
+
# Check for exit commands
|
| 243 |
+
if user_query.lower() in ['quit', 'exit', 'q']:
|
| 244 |
+
print("👋 Goodbye!")
|
| 245 |
+
break
|
| 246 |
+
|
| 247 |
+
# Check for help command
|
| 248 |
+
if user_query.lower() == 'help':
|
| 249 |
+
print("\n📋 Example questions you can ask:")
|
| 250 |
+
print(" • Is heart surgery covered under this policy?")
|
| 251 |
+
print(" • What is the waiting period for pre-existing diseases?")
|
| 252 |
+
print(" • Can I claim for dental treatment?")
|
| 253 |
+
print(" • What is the maximum coverage amount?")
|
| 254 |
+
print(" • Are there any exclusions for chronic diseases?")
|
| 255 |
+
print(" • What documents are required for claim submission?")
|
| 256 |
+
print(" • Is cancer treatment covered?")
|
| 257 |
+
print(" • What is the claim process?")
|
| 258 |
+
print(" • Does the policy cover newborn care after hospital discharge?")
|
| 259 |
+
print()
|
| 260 |
+
continue
|
| 261 |
+
|
| 262 |
+
# Skip empty queries
|
| 263 |
+
if not user_query:
|
| 264 |
+
print("⚠️ Please enter a question.")
|
| 265 |
+
continue
|
| 266 |
+
|
| 267 |
+
query_count += 1
|
| 268 |
+
print(f"\n🔍 Processing Query #{query_count}")
|
| 269 |
+
print(f"🤔 Query: {user_query}")
|
| 270 |
+
|
| 271 |
+
# Process the query
|
| 272 |
+
start_time = time.time()
|
| 273 |
+
try:
|
| 274 |
+
result = rag_system.process_query(user_query)
|
| 275 |
+
processing_time = time.time() - start_time
|
| 276 |
+
except Exception as e:
|
| 277 |
+
print(f"❌ Query processing failed: {e}")
|
| 278 |
+
print("💡 Creating fallback result...")
|
| 279 |
+
|
| 280 |
+
# Create fallback result
|
| 281 |
+
from query_parser import AdvancedQueryParser
|
| 282 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 283 |
+
|
| 284 |
+
try:
|
| 285 |
+
# Parse query
|
| 286 |
+
query_parser = AdvancedQueryParser()
|
| 287 |
+
parsed_query = query_parser.parse_query(user_query)
|
| 288 |
+
|
| 289 |
+
# Create fallback reasoning
|
| 290 |
+
reasoning_engine = AdvancedLLMReasoning()
|
| 291 |
+
fallback_context = [{
|
| 292 |
+
'content': f"Based on the query: {user_query}",
|
| 293 |
+
'source_file': 'fallback',
|
| 294 |
+
'similarity_score': 0.5
|
| 295 |
+
}]
|
| 296 |
+
|
| 297 |
+
reasoning_result = reasoning_engine.analyze_query(
|
| 298 |
+
user_query, fallback_context, parsed_query.query_type
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
# Create fallback result
|
| 302 |
+
from rag_system import QueryResult
|
| 303 |
+
result = QueryResult(
|
| 304 |
+
query=user_query,
|
| 305 |
+
parsed_query=parsed_query,
|
| 306 |
+
search_results=[],
|
| 307 |
+
reasoning_result=reasoning_result,
|
| 308 |
+
processing_time=time.time() - start_time,
|
| 309 |
+
timestamp=time.time(),
|
| 310 |
+
audit_trail={'status': 'fallback', 'error': str(e)}
|
| 311 |
+
)
|
| 312 |
+
processing_time = time.time() - start_time
|
| 313 |
+
|
| 314 |
+
except Exception as fallback_error:
|
| 315 |
+
print(f"❌ Fallback processing also failed: {fallback_error}")
|
| 316 |
+
continue
|
| 317 |
+
|
| 318 |
+
# Display results
|
| 319 |
+
print(f"\n📋 QUERY RESULTS")
|
| 320 |
+
print("=" * 50)
|
| 321 |
+
print(f"⏱️ Processing time: {processing_time:.2f} seconds")
|
| 322 |
+
print(f"🎯 Decision: {result.reasoning_result.decision.upper()}")
|
| 323 |
+
print(f"📊 Confidence: {result.reasoning_result.confidence_score:.1%}")
|
| 324 |
+
|
| 325 |
+
if result.reasoning_result.amount:
|
| 326 |
+
print(f"💰 Amount: ${result.reasoning_result.amount:,.2f}")
|
| 327 |
+
|
| 328 |
+
if result.reasoning_result.waiting_period:
|
| 329 |
+
print(f"⏰ Waiting Period: {result.reasoning_result.waiting_period}")
|
| 330 |
+
|
| 331 |
+
print(f"\n📝 Justification:")
|
| 332 |
+
print(result.reasoning_result.justification)
|
| 333 |
+
|
| 334 |
+
if result.reasoning_result.relevant_clauses:
|
| 335 |
+
print(f"\n📄 Relevant Clauses:")
|
| 336 |
+
for clause in result.reasoning_result.relevant_clauses:
|
| 337 |
+
print(f" • {clause}")
|
| 338 |
+
|
| 339 |
+
if result.reasoning_result.conditions:
|
| 340 |
+
print(f"\n✅ Conditions:")
|
| 341 |
+
for condition in result.reasoning_result.conditions:
|
| 342 |
+
print(f" • {condition}")
|
| 343 |
+
|
| 344 |
+
if result.reasoning_result.exclusions:
|
| 345 |
+
print(f"\n❌ Exclusions:")
|
| 346 |
+
for exclusion in result.reasoning_result.exclusions:
|
| 347 |
+
print(f" • {exclusion}")
|
| 348 |
+
|
| 349 |
+
if result.reasoning_result.required_documents:
|
| 350 |
+
print(f"\n📋 Required Documents:")
|
| 351 |
+
for doc in result.reasoning_result.required_documents:
|
| 352 |
+
print(f" • {doc}")
|
| 353 |
+
|
| 354 |
+
# Show search results summary
|
| 355 |
+
if result.search_results:
|
| 356 |
+
print(f"\n🔍 Search Results Summary:")
|
| 357 |
+
print(f" Found {len(result.search_results)} relevant documents")
|
| 358 |
+
for i, search_result in enumerate(result.search_results[:3], 1):
|
| 359 |
+
print(f" {i}. {search_result.source_file} (Score: {search_result.similarity_score:.2f})")
|
| 360 |
+
|
| 361 |
+
# Ask if user wants to see detailed explanation
|
| 362 |
+
show_details = input("\n❓ Show detailed explanation? (y/n): ").strip().lower()
|
| 363 |
+
if show_details in ['y', 'yes']:
|
| 364 |
+
try:
|
| 365 |
+
detailed_explanation = rag_system.reasoning_engine.explain_decision(result.reasoning_result)
|
| 366 |
+
print(f"\n📖 DETAILED EXPLANATION:")
|
| 367 |
+
print("=" * 50)
|
| 368 |
+
print(detailed_explanation)
|
| 369 |
+
except Exception as e:
|
| 370 |
+
print(f"❌ Error generating detailed explanation: {e}")
|
| 371 |
+
print("💡 Detailed explanation not available")
|
| 372 |
+
|
| 373 |
+
results.append({
|
| 374 |
+
"query": user_query,
|
| 375 |
+
"result": result,
|
| 376 |
+
"processing_time": processing_time
|
| 377 |
+
})
|
| 378 |
+
|
| 379 |
+
print()
|
| 380 |
+
|
| 381 |
+
# Ask if user wants to continue
|
| 382 |
+
if query_count % 3 == 0: # Ask every 3 queries
|
| 383 |
+
continue_choice = input("❓ Continue asking questions? (y/n): ").strip().lower()
|
| 384 |
+
if continue_choice not in ['y', 'yes', '']:
|
| 385 |
+
print("👋 Thanks for using the RAG system!")
|
| 386 |
+
break
|
| 387 |
+
|
| 388 |
+
except KeyboardInterrupt:
|
| 389 |
+
print("\n👋 Interrupted by user. Goodbye!")
|
| 390 |
+
break
|
| 391 |
+
except Exception as e:
|
| 392 |
+
print(f"❌ Error processing query: {e}")
|
| 393 |
+
print("💡 Try asking a different question or type 'help' for examples.")
|
| 394 |
+
print()
|
| 395 |
+
|
| 396 |
+
return results
|
| 397 |
+
|
| 398 |
+
def show_system_statistics(rag_system: AdvancedRAGSystem):
|
| 399 |
+
"""Show system statistics"""
|
| 400 |
+
print("\n📊 SYSTEM STATISTICS")
|
| 401 |
+
print("-" * 40)
|
| 402 |
+
|
| 403 |
+
try:
|
| 404 |
+
stats = rag_system.get_system_statistics()
|
| 405 |
+
|
| 406 |
+
if stats:
|
| 407 |
+
# Vector database stats
|
| 408 |
+
db_stats = stats.get('vector_database', {})
|
| 409 |
+
print(f"📚 Vector Database:")
|
| 410 |
+
print(f" Total chunks: {db_stats.get('total_chunks', 0)}")
|
| 411 |
+
print(f" Unique sources: {db_stats.get('unique_sources', 0)}")
|
| 412 |
+
print(f" File types: {', '.join(db_stats.get('file_types', []))}")
|
| 413 |
+
|
| 414 |
+
# Audit trail stats
|
| 415 |
+
audit_stats = stats.get('audit_trail', {})
|
| 416 |
+
print(f"\n📋 Audit Trail:")
|
| 417 |
+
print(f" Total entries: {audit_stats.get('total_entries', 0)}")
|
| 418 |
+
print(f" Successful queries: {audit_stats.get('successful_queries', 0)}")
|
| 419 |
+
print(f" Failed queries: {audit_stats.get('failed_queries', 0)}")
|
| 420 |
+
print(f" Document ingestions: {audit_stats.get('document_ingestions', 0)}")
|
| 421 |
+
|
| 422 |
+
# Component info
|
| 423 |
+
components = stats.get('components', {})
|
| 424 |
+
print(f"\n🔧 Components:")
|
| 425 |
+
print(f" Document Processor: {components.get('document_processor', 'Unknown')}")
|
| 426 |
+
print(f" Vector Database: {components.get('vector_database', 'Unknown')}")
|
| 427 |
+
print(f" Query Parser: {components.get('query_parser', 'Unknown')}")
|
| 428 |
+
print(f" Reasoning Engine: {components.get('reasoning_engine', 'Unknown')}")
|
| 429 |
+
print(f" GPU Enabled: {components.get('use_gpu', False)}")
|
| 430 |
+
else:
|
| 431 |
+
print("❌ Unable to retrieve system statistics")
|
| 432 |
+
|
| 433 |
+
except Exception as e:
|
| 434 |
+
print(f"❌ Error getting system statistics: {e}")
|
| 435 |
+
|
| 436 |
+
def show_audit_trail(rag_system: AdvancedRAGSystem):
|
| 437 |
+
"""Show audit trail"""
|
| 438 |
+
print("\n🔍 AUDIT TRAIL")
|
| 439 |
+
print("-" * 40)
|
| 440 |
+
|
| 441 |
+
try:
|
| 442 |
+
audit_log = rag_system.get_audit_trail()
|
| 443 |
+
|
| 444 |
+
if audit_log:
|
| 445 |
+
print(f"📋 Total entries: {len(audit_log)}")
|
| 446 |
+
|
| 447 |
+
# Show recent entries
|
| 448 |
+
recent_entries = audit_log[-10:] # Last 10 entries
|
| 449 |
+
print(f"\n📝 Recent Entries:")
|
| 450 |
+
|
| 451 |
+
for i, entry in enumerate(reversed(recent_entries), 1):
|
| 452 |
+
action = entry.get('action', 'Unknown')
|
| 453 |
+
timestamp = entry.get('timestamp', 'Unknown')
|
| 454 |
+
status = entry.get('status', 'Unknown')
|
| 455 |
+
|
| 456 |
+
print(f" {i}. {action} - {status} ({timestamp})")
|
| 457 |
+
|
| 458 |
+
if action == 'query_processing':
|
| 459 |
+
query = entry.get('query', 'Unknown')
|
| 460 |
+
print(f" Query: {query[:50]}...")
|
| 461 |
+
|
| 462 |
+
reasoning = entry.get('reasoning_result', {})
|
| 463 |
+
if reasoning:
|
| 464 |
+
decision = reasoning.get('decision', 'Unknown')
|
| 465 |
+
confidence = reasoning.get('confidence_score', 0.0)
|
| 466 |
+
print(f" Decision: {decision} (Confidence: {confidence:.1%})")
|
| 467 |
+
|
| 468 |
+
elif action == 'document_ingestion':
|
| 469 |
+
file_path = entry.get('file_path', 'Unknown')
|
| 470 |
+
chunks = entry.get('chunks_processed', 0)
|
| 471 |
+
print(f" File: {os.path.basename(file_path)} ({chunks} chunks)")
|
| 472 |
+
|
| 473 |
+
print()
|
| 474 |
+
|
| 475 |
+
# Ask if user wants to save audit trail
|
| 476 |
+
save_choice = input("💾 Save audit trail to file? (y/n): ").strip().lower()
|
| 477 |
+
if save_choice in ['y', 'yes']:
|
| 478 |
+
filename = input("📁 Enter filename (default: audit_trail.json): ").strip()
|
| 479 |
+
if not filename:
|
| 480 |
+
filename = "audit_trail.json"
|
| 481 |
+
|
| 482 |
+
if rag_system.save_audit_trail(filename):
|
| 483 |
+
print(f"✅ Audit trail saved to: {filename}")
|
| 484 |
+
else:
|
| 485 |
+
print("❌ Failed to save audit trail")
|
| 486 |
+
else:
|
| 487 |
+
print("📋 No audit trail entries found")
|
| 488 |
+
|
| 489 |
+
except Exception as e:
|
| 490 |
+
print(f"❌ Error showing audit trail: {e}")
|
| 491 |
+
|
| 492 |
+
def validate_system(rag_system: AdvancedRAGSystem):
|
| 493 |
+
"""Validate system components"""
|
| 494 |
+
print("\n🧪 SYSTEM VALIDATION")
|
| 495 |
+
print("-" * 40)
|
| 496 |
+
|
| 497 |
+
try:
|
| 498 |
+
validation = rag_system.validate_system()
|
| 499 |
+
|
| 500 |
+
print("🔍 Checking system components...")
|
| 501 |
+
|
| 502 |
+
components = [
|
| 503 |
+
('Document Processor', validation.get('document_processor', False)),
|
| 504 |
+
('Vector Database', validation.get('vector_database', False)),
|
| 505 |
+
('Query Parser', validation.get('query_parser', False)),
|
| 506 |
+
('Reasoning Engine', validation.get('reasoning_engine', False))
|
| 507 |
+
]
|
| 508 |
+
|
| 509 |
+
all_valid = True
|
| 510 |
+
for component_name, is_valid in components:
|
| 511 |
+
status = "✅ PASS" if is_valid else "❌ FAIL"
|
| 512 |
+
print(f" {component_name}: {status}")
|
| 513 |
+
if not is_valid:
|
| 514 |
+
all_valid = False
|
| 515 |
+
|
| 516 |
+
print(f"\n🎯 Overall Status: {'✅ ALL COMPONENTS VALID' if all_valid else '❌ SOME COMPONENTS FAILED'}")
|
| 517 |
+
|
| 518 |
+
if not all_valid:
|
| 519 |
+
print("\n❌ Errors found:")
|
| 520 |
+
for error in validation.get('errors', []):
|
| 521 |
+
print(f" • {error}")
|
| 522 |
+
|
| 523 |
+
except Exception as e:
|
| 524 |
+
print(f"❌ Error validating system: {e}")
|
| 525 |
+
|
| 526 |
+
def export_system_data(rag_system: AdvancedRAGSystem):
|
| 527 |
+
"""Export system data"""
|
| 528 |
+
print("\n💾 EXPORT SYSTEM DATA")
|
| 529 |
+
print("-" * 40)
|
| 530 |
+
|
| 531 |
+
try:
|
| 532 |
+
filename = input("📁 Enter filename (default: system_export.json): ").strip()
|
| 533 |
+
if not filename:
|
| 534 |
+
filename = "system_export.json"
|
| 535 |
+
|
| 536 |
+
print(f"⏳ Exporting system data to: {filename}")
|
| 537 |
+
|
| 538 |
+
if rag_system.export_system_data(filename):
|
| 539 |
+
print(f"✅ System data exported successfully to: {filename}")
|
| 540 |
+
|
| 541 |
+
# Show file size
|
| 542 |
+
if os.path.exists(filename):
|
| 543 |
+
file_size = os.path.getsize(filename)
|
| 544 |
+
print(f"📁 File size: {file_size:,} bytes")
|
| 545 |
+
else:
|
| 546 |
+
print("❌ Failed to export system data")
|
| 547 |
+
|
| 548 |
+
except Exception as e:
|
| 549 |
+
print(f"❌ Error exporting system data: {e}")
|
| 550 |
+
|
| 551 |
+
def clear_system(rag_system: AdvancedRAGSystem):
|
| 552 |
+
"""Clear system data"""
|
| 553 |
+
print("\n🗑️ CLEAR SYSTEM")
|
| 554 |
+
print("-" * 40)
|
| 555 |
+
|
| 556 |
+
try:
|
| 557 |
+
confirm = input("⚠️ This will clear ALL system data. Are you sure? (yes/no): ").strip().lower()
|
| 558 |
+
|
| 559 |
+
if confirm == 'yes':
|
| 560 |
+
print("⏳ Clearing system data...")
|
| 561 |
+
|
| 562 |
+
if rag_system.clear_system():
|
| 563 |
+
print("✅ System data cleared successfully")
|
| 564 |
+
else:
|
| 565 |
+
print("❌ Failed to clear system data")
|
| 566 |
+
else:
|
| 567 |
+
print("❌ Operation cancelled")
|
| 568 |
+
|
| 569 |
+
except Exception as e:
|
| 570 |
+
print(f"❌ Error clearing system: {e}")
|
| 571 |
+
|
| 572 |
+
def test_ocr():
|
| 573 |
+
"""Test OCR functionality"""
|
| 574 |
+
print("\n🧪 OCR TEST")
|
| 575 |
+
print("-" * 40)
|
| 576 |
+
|
| 577 |
+
if not OCR_AVAILABLE:
|
| 578 |
+
print("❌ pytesseract not available")
|
| 579 |
+
print("💡 Install with: pip install pytesseract")
|
| 580 |
+
return
|
| 581 |
+
|
| 582 |
+
try:
|
| 583 |
+
# Create a simple test image
|
| 584 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 585 |
+
|
| 586 |
+
# Create test image
|
| 587 |
+
img = Image.new('RGB', (300, 100), color='white')
|
| 588 |
+
draw = ImageDraw.Draw(img)
|
| 589 |
+
|
| 590 |
+
# Try to use a default font
|
| 591 |
+
try:
|
| 592 |
+
font = ImageFont.load_default()
|
| 593 |
+
except:
|
| 594 |
+
font = None
|
| 595 |
+
|
| 596 |
+
# Draw text
|
| 597 |
+
text = "Test OCR Text"
|
| 598 |
+
draw.text((10, 40), text, fill='black', font=font)
|
| 599 |
+
|
| 600 |
+
# Test OCR
|
| 601 |
+
ocr_text = pytesseract.image_to_string(img)
|
| 602 |
+
print(f"✅ OCR test successful")
|
| 603 |
+
print(f" Original: '{text}'")
|
| 604 |
+
print(f" OCR result: '{ocr_text.strip()}'")
|
| 605 |
+
|
| 606 |
+
except Exception as e:
|
| 607 |
+
print(f"❌ OCR test failed: {e}")
|
| 608 |
+
print("💡 Make sure Tesseract is installed and configured")
|
| 609 |
+
|
| 610 |
+
def show_help():
|
| 611 |
+
"""Show help information"""
|
| 612 |
+
print("\n❓ HELP")
|
| 613 |
+
print("-" * 40)
|
| 614 |
+
print("📋 This RAG system can process various document types and answer questions about them.")
|
| 615 |
+
print("\n📄 Supported Document Types:")
|
| 616 |
+
print(" • PDF files (with OCR support for scanned documents)")
|
| 617 |
+
print(" • Text files (.txt)")
|
| 618 |
+
print(" • Word documents (.docx)")
|
| 619 |
+
print(" • HTML files (.html)")
|
| 620 |
+
print(" • Email files (.eml, .msg)")
|
| 621 |
+
print(" • CSV files (.csv)")
|
| 622 |
+
print(" • JSON files (.json)")
|
| 623 |
+
|
| 624 |
+
print("\n🤔 Query Examples:")
|
| 625 |
+
print(" • 'Is heart surgery covered under this policy?'")
|
| 626 |
+
print(" • 'How do I file a claim?'")
|
| 627 |
+
print(" • 'What is the waiting period for pre-existing conditions?'")
|
| 628 |
+
print(" • 'What documents are required for claim submission?'")
|
| 629 |
+
print(" • 'What is the maximum coverage amount?'")
|
| 630 |
+
|
| 631 |
+
print("\n💡 Tips:")
|
| 632 |
+
print(" • Upload documents first before asking questions")
|
| 633 |
+
print(" • Use natural language - the system understands plain English")
|
| 634 |
+
print(" • The system can handle vague or incomplete queries")
|
| 635 |
+
print(" • All queries are logged in the audit trail")
|
| 636 |
+
print(" • Use the system validation to check component status")
|
| 637 |
+
print(" • Test OCR functionality if you have issues with scanned documents")
|
| 638 |
+
|
| 639 |
+
def main():
|
| 640 |
+
"""Main function"""
|
| 641 |
+
# Parse command line arguments
|
| 642 |
+
parser = argparse.ArgumentParser(description='Advanced RAG System')
|
| 643 |
+
parser.add_argument('--query', help='Process a single query from file')
|
| 644 |
+
parser.add_argument('--upload', help='Upload and process a document')
|
| 645 |
+
parser.add_argument('--status', action='store_true', help='Check system status')
|
| 646 |
+
|
| 647 |
+
args = parser.parse_args()
|
| 648 |
+
|
| 649 |
+
# Handle command line arguments
|
| 650 |
+
if args.query:
|
| 651 |
+
# Process single query from file
|
| 652 |
+
try:
|
| 653 |
+
with open(args.query, 'r') as f:
|
| 654 |
+
question = f.read().strip()
|
| 655 |
+
|
| 656 |
+
# Initialize RAG system
|
| 657 |
+
rag_system = AdvancedRAGSystem(use_gpu=False)
|
| 658 |
+
|
| 659 |
+
# Process the query
|
| 660 |
+
result = rag_system.process_query(question)
|
| 661 |
+
|
| 662 |
+
# Output results in a structured format
|
| 663 |
+
print(f"Question: {question}")
|
| 664 |
+
print(f"Decision: {result.reasoning_result.decision}")
|
| 665 |
+
print(f"Confidence: {result.reasoning_result.confidence_score:.1%}")
|
| 666 |
+
print(f"Justification: {result.reasoning_result.justification}")
|
| 667 |
+
|
| 668 |
+
if result.reasoning_result.amount:
|
| 669 |
+
print(f"Amount: ${result.reasoning_result.amount:,.2f}")
|
| 670 |
+
|
| 671 |
+
if result.reasoning_result.waiting_period:
|
| 672 |
+
print(f"Waiting Period: {result.reasoning_result.waiting_period}")
|
| 673 |
+
|
| 674 |
+
if result.reasoning_result.relevant_clauses:
|
| 675 |
+
print(f"Relevant Clauses: {', '.join(result.reasoning_result.relevant_clauses)}")
|
| 676 |
+
|
| 677 |
+
if result.reasoning_result.conditions:
|
| 678 |
+
print(f"Conditions: {', '.join(result.reasoning_result.conditions)}")
|
| 679 |
+
|
| 680 |
+
if result.reasoning_result.exclusions:
|
| 681 |
+
print(f"Exclusions: {', '.join(result.reasoning_result.exclusions)}")
|
| 682 |
+
|
| 683 |
+
if result.reasoning_result.required_documents:
|
| 684 |
+
print(f"Required Documents: {', '.join(result.reasoning_result.required_documents)}")
|
| 685 |
+
|
| 686 |
+
return
|
| 687 |
+
except Exception as e:
|
| 688 |
+
print(f"Error processing query: {e}")
|
| 689 |
+
sys.exit(1)
|
| 690 |
+
|
| 691 |
+
elif args.upload:
|
| 692 |
+
# Upload and process document
|
| 693 |
+
try:
|
| 694 |
+
# Initialize RAG system
|
| 695 |
+
rag_system = AdvancedRAGSystem(use_gpu=False)
|
| 696 |
+
|
| 697 |
+
# Process the document
|
| 698 |
+
chunks = rag_system.ingest_document(args.upload, use_ocr=False)
|
| 699 |
+
|
| 700 |
+
print(f"Document processed successfully: {args.upload}")
|
| 701 |
+
print(f"Chunks processed: {len(chunks)}")
|
| 702 |
+
|
| 703 |
+
return
|
| 704 |
+
except Exception as e:
|
| 705 |
+
print(f"Error processing document: {e}")
|
| 706 |
+
sys.exit(1)
|
| 707 |
+
|
| 708 |
+
elif args.status:
|
| 709 |
+
# Check system status
|
| 710 |
+
try:
|
| 711 |
+
rag_system = AdvancedRAGSystem(use_gpu=False)
|
| 712 |
+
print("System Status: READY")
|
| 713 |
+
return
|
| 714 |
+
except Exception as e:
|
| 715 |
+
print(f"System Status: ERROR - {e}")
|
| 716 |
+
sys.exit(1)
|
| 717 |
+
|
| 718 |
+
# Interactive mode (default)
|
| 719 |
+
print_banner()
|
| 720 |
+
|
| 721 |
+
try:
|
| 722 |
+
# Initialize RAG system
|
| 723 |
+
print("🚀 Initializing RAG system...")
|
| 724 |
+
try:
|
| 725 |
+
rag_system = AdvancedRAGSystem(use_gpu=False) # Use CPU for better compatibility
|
| 726 |
+
print("✅ RAG system initialized successfully!")
|
| 727 |
+
except Exception as e:
|
| 728 |
+
print(f"❌ Failed to initialize RAG system: {e}")
|
| 729 |
+
print("💡 Trying with minimal configuration...")
|
| 730 |
+
|
| 731 |
+
try:
|
| 732 |
+
# Try with minimal settings
|
| 733 |
+
rag_system = AdvancedRAGSystem(
|
| 734 |
+
use_gpu=False,
|
| 735 |
+
model_path=None # Don't require model file
|
| 736 |
+
)
|
| 737 |
+
print("✅ RAG system initialized with minimal configuration!")
|
| 738 |
+
except Exception as e2:
|
| 739 |
+
print(f"❌ RAG system initialization failed: {e2}")
|
| 740 |
+
print("💡 Please check your system configuration")
|
| 741 |
+
return
|
| 742 |
+
|
| 743 |
+
# Main loop
|
| 744 |
+
while True:
|
| 745 |
+
print_menu()
|
| 746 |
+
choice = get_user_choice()
|
| 747 |
+
|
| 748 |
+
if choice == '1':
|
| 749 |
+
upload_document(rag_system)
|
| 750 |
+
elif choice == '2':
|
| 751 |
+
process_query(rag_system)
|
| 752 |
+
elif choice == '3':
|
| 753 |
+
show_system_statistics(rag_system)
|
| 754 |
+
elif choice == '4':
|
| 755 |
+
show_audit_trail(rag_system)
|
| 756 |
+
elif choice == '5':
|
| 757 |
+
validate_system(rag_system)
|
| 758 |
+
elif choice == '6':
|
| 759 |
+
export_system_data(rag_system)
|
| 760 |
+
elif choice == '7':
|
| 761 |
+
clear_system(rag_system)
|
| 762 |
+
elif choice == '8':
|
| 763 |
+
test_ocr()
|
| 764 |
+
elif choice == '9':
|
| 765 |
+
show_help()
|
| 766 |
+
elif choice == '0':
|
| 767 |
+
print("👋 Thank you for using the Advanced RAG System!")
|
| 768 |
+
break
|
| 769 |
+
else:
|
| 770 |
+
print("❌ Invalid choice. Please enter a number between 1-0.")
|
| 771 |
+
|
| 772 |
+
# Pause before showing menu again
|
| 773 |
+
input("\n⏸️ Press Enter to continue...")
|
| 774 |
+
|
| 775 |
+
except KeyboardInterrupt:
|
| 776 |
+
print("\n👋 Goodbye!")
|
| 777 |
+
except Exception as e:
|
| 778 |
+
print(f"❌ Fatal error: {e}")
|
| 779 |
+
print("💡 Please check your system configuration and try again.")
|
| 780 |
+
|
| 781 |
+
if __name__ == "__main__":
|
| 782 |
+
main()
|
performance_analysis.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Performance analysis script to identify bottlenecks
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
import cProfile
|
| 10 |
+
import pstats
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
def analyze_performance():
|
| 14 |
+
"""Analyze performance of each component"""
|
| 15 |
+
print("⚡ PERFORMANCE ANALYSIS")
|
| 16 |
+
print("=" * 50)
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
from rag_system import AdvancedRAGSystem
|
| 20 |
+
from document_processer import AdvancedDocumentProcessor
|
| 21 |
+
from vector_database import VectorDatabase
|
| 22 |
+
from query_parser import AdvancedQueryParser
|
| 23 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 24 |
+
|
| 25 |
+
file_path = "doc2.pdf"
|
| 26 |
+
if not os.path.exists(file_path):
|
| 27 |
+
print(f"❌ File not found: {file_path}")
|
| 28 |
+
return
|
| 29 |
+
|
| 30 |
+
print(f"📄 Testing with file: {file_path}")
|
| 31 |
+
|
| 32 |
+
# Test 1: Document Processing Performance
|
| 33 |
+
print("\n1️⃣ DOCUMENT PROCESSING PERFORMANCE")
|
| 34 |
+
print("-" * 40)
|
| 35 |
+
|
| 36 |
+
doc_processor = AdvancedDocumentProcessor()
|
| 37 |
+
start_time = time.time()
|
| 38 |
+
chunks = doc_processor.process_document(file_path, use_ocr=False)
|
| 39 |
+
doc_time = time.time() - start_time
|
| 40 |
+
|
| 41 |
+
print(f"✅ Document processing: {doc_time:.2f}s")
|
| 42 |
+
print(f"📊 Chunks created: {len(chunks)}")
|
| 43 |
+
print(f"📊 Average time per chunk: {doc_time/len(chunks):.4f}s")
|
| 44 |
+
|
| 45 |
+
# Test 2: Vector Database Performance
|
| 46 |
+
print("\n2️⃣ VECTOR DATABASE PERFORMANCE")
|
| 47 |
+
print("-" * 40)
|
| 48 |
+
|
| 49 |
+
vector_db = VectorDatabase()
|
| 50 |
+
start_time = time.time()
|
| 51 |
+
success = vector_db.add_documents(chunks)
|
| 52 |
+
vector_time = time.time() - start_time
|
| 53 |
+
|
| 54 |
+
print(f"✅ Vector database addition: {vector_time:.2f}s")
|
| 55 |
+
print(f"📊 Success: {success}")
|
| 56 |
+
print(f"📊 Average time per chunk: {vector_time/len(chunks):.4f}s")
|
| 57 |
+
|
| 58 |
+
# Test 3: Query Parser Performance
|
| 59 |
+
print("\n3️⃣ QUERY PARSER PERFORMANCE")
|
| 60 |
+
print("-" * 40)
|
| 61 |
+
|
| 62 |
+
query_parser = AdvancedQueryParser()
|
| 63 |
+
test_query = "Does the policy cover newborn care after hospital discharge?"
|
| 64 |
+
|
| 65 |
+
start_time = time.time()
|
| 66 |
+
parsed = query_parser.parse_query(test_query)
|
| 67 |
+
parser_time = time.time() - start_time
|
| 68 |
+
|
| 69 |
+
print(f"✅ Query parsing: {parser_time:.2f}s")
|
| 70 |
+
print(f"📊 Query type: {parsed.query_type}")
|
| 71 |
+
print(f"📊 Confidence: {parsed.confidence}")
|
| 72 |
+
|
| 73 |
+
# Test 4: LLM Reasoning Performance
|
| 74 |
+
print("\n4️⃣ LLM REASONING PERFORMANCE")
|
| 75 |
+
print("-" * 40)
|
| 76 |
+
|
| 77 |
+
reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
|
| 78 |
+
test_context = [{"content": "Sample policy content", "source_file": "test.pdf"}]
|
| 79 |
+
|
| 80 |
+
start_time = time.time()
|
| 81 |
+
result = reasoning_engine.analyze_query(test_query, test_context, "coverage_inquiry")
|
| 82 |
+
reasoning_time = time.time() - start_time
|
| 83 |
+
|
| 84 |
+
print(f"✅ LLM reasoning: {reasoning_time:.2f}s")
|
| 85 |
+
print(f"📊 Decision: {result.decision}")
|
| 86 |
+
print(f"📊 Confidence: {result.confidence_score}")
|
| 87 |
+
|
| 88 |
+
# Test 5: Full RAG System Performance
|
| 89 |
+
print("\n5️⃣ FULL RAG SYSTEM PERFORMANCE")
|
| 90 |
+
print("-" * 40)
|
| 91 |
+
|
| 92 |
+
rag_system = AdvancedRAGSystem(use_gpu=False)
|
| 93 |
+
|
| 94 |
+
# Document ingestion
|
| 95 |
+
start_time = time.time()
|
| 96 |
+
rag_chunks = rag_system.ingest_document(file_path, use_ocr=False)
|
| 97 |
+
ingestion_time = time.time() - start_time
|
| 98 |
+
|
| 99 |
+
print(f"✅ Document ingestion: {ingestion_time:.2f}s")
|
| 100 |
+
print(f"📊 Chunks ingested: {len(rag_chunks)}")
|
| 101 |
+
|
| 102 |
+
# Query processing
|
| 103 |
+
start_time = time.time()
|
| 104 |
+
query_result = rag_system.process_query(test_query)
|
| 105 |
+
query_time = time.time() - start_time
|
| 106 |
+
|
| 107 |
+
print(f"✅ Query processing: {query_time:.2f}s")
|
| 108 |
+
print(f"📊 Total time: {ingestion_time + query_time:.2f}s")
|
| 109 |
+
|
| 110 |
+
# Performance Summary
|
| 111 |
+
print("\n📊 PERFORMANCE SUMMARY")
|
| 112 |
+
print("=" * 50)
|
| 113 |
+
print(f"Document Processing: {doc_time:.2f}s ({doc_time/(ingestion_time + query_time)*100:.1f}%)")
|
| 114 |
+
print(f"Vector Database: {vector_time:.2f}s ({vector_time/(ingestion_time + query_time)*100:.1f}%)")
|
| 115 |
+
print(f"Query Parsing: {parser_time:.2f}s ({parser_time/(ingestion_time + query_time)*100:.1f}%)")
|
| 116 |
+
print(f"LLM Reasoning: {reasoning_time:.2f}s ({reasoning_time/(ingestion_time + query_time)*100:.1f}%)")
|
| 117 |
+
print(f"TOTAL TIME: {ingestion_time + query_time:.2f}s")
|
| 118 |
+
|
| 119 |
+
# Optimization Recommendations
|
| 120 |
+
print("\n💡 OPTIMIZATION RECOMMENDATIONS")
|
| 121 |
+
print("=" * 50)
|
| 122 |
+
|
| 123 |
+
if doc_time > 10:
|
| 124 |
+
print("🔧 Document processing is slow - consider:")
|
| 125 |
+
print(" - Reduce chunk size")
|
| 126 |
+
print(" - Use parallel processing")
|
| 127 |
+
print(" - Optimize OCR settings")
|
| 128 |
+
|
| 129 |
+
if vector_time > 20:
|
| 130 |
+
print("🔧 Vector database is slow - consider:")
|
| 131 |
+
print(" - Use GPU for embeddings")
|
| 132 |
+
print(" - Batch processing")
|
| 133 |
+
print(" - Reduce embedding dimensions")
|
| 134 |
+
|
| 135 |
+
if reasoning_time > 30:
|
| 136 |
+
print("🔧 LLM reasoning is slow - consider:")
|
| 137 |
+
print(" - Use smaller model")
|
| 138 |
+
print(" - Enable GPU acceleration")
|
| 139 |
+
print(" - Reduce max tokens")
|
| 140 |
+
print(" - Use caching")
|
| 141 |
+
|
| 142 |
+
if ingestion_time + query_time > 30:
|
| 143 |
+
print("🔧 Overall system is slow - consider:")
|
| 144 |
+
print(" - Enable GPU for all components")
|
| 145 |
+
print(" - Use model quantization")
|
| 146 |
+
print(" - Implement caching")
|
| 147 |
+
print(" - Parallel processing")
|
| 148 |
+
|
| 149 |
+
except Exception as e:
|
| 150 |
+
print(f"❌ Error: {e}")
|
| 151 |
+
import traceback
|
| 152 |
+
traceback.print_exc()
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
analyze_performance()
|
query_parser.py
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Advanced Query Parser for Natural Language Processing
|
| 3 |
+
Handles vague, incomplete, and plain English queries with entity extraction
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import re
|
| 7 |
+
import logging
|
| 8 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
# NLP and ML libraries
|
| 14 |
+
# import spacy # Temporarily commented out due to installation issues
|
| 15 |
+
# from transformers import pipeline # Temporarily commented out due to installation issues
|
| 16 |
+
import nltk
|
| 17 |
+
from nltk.tokenize import word_tokenize, sent_tokenize
|
| 18 |
+
from nltk.corpus import stopwords
|
| 19 |
+
from nltk.stem import WordNetLemmatizer
|
| 20 |
+
import numpy as np
|
| 21 |
+
|
| 22 |
+
# Optional imports with error handling
|
| 23 |
+
try:
|
| 24 |
+
from transformers import pipeline
|
| 25 |
+
TRANSFORMERS_AVAILABLE = True
|
| 26 |
+
except ImportError:
|
| 27 |
+
TRANSFORMERS_AVAILABLE = False
|
| 28 |
+
print("⚠️ Transformers not available. NER functionality will be limited.")
|
| 29 |
+
|
| 30 |
+
# Download required NLTK data
|
| 31 |
+
try:
|
| 32 |
+
nltk.data.find('tokenizers/punkt')
|
| 33 |
+
except LookupError:
|
| 34 |
+
nltk.download('punkt')
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
nltk.data.find('corpora/stopwords')
|
| 38 |
+
except LookupError:
|
| 39 |
+
nltk.download('stopwords')
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
nltk.data.find('corpora/wordnet')
|
| 43 |
+
except LookupError:
|
| 44 |
+
nltk.download('wordnet')
|
| 45 |
+
|
| 46 |
+
# Additional NLTK data that might be needed
|
| 47 |
+
try:
|
| 48 |
+
nltk.data.find('tokenizers/punkt_tab')
|
| 49 |
+
except LookupError:
|
| 50 |
+
try:
|
| 51 |
+
nltk.download('punkt_tab')
|
| 52 |
+
except:
|
| 53 |
+
pass # Ignore if punkt_tab is not available
|
| 54 |
+
|
| 55 |
+
# Configure logging
|
| 56 |
+
logging.basicConfig(level=logging.INFO)
|
| 57 |
+
logger = logging.getLogger(__name__)
|
| 58 |
+
|
| 59 |
+
@dataclass
|
| 60 |
+
class ParsedQuery:
|
| 61 |
+
"""Represents a parsed query with extracted information"""
|
| 62 |
+
original_query: str
|
| 63 |
+
enhanced_query: str
|
| 64 |
+
query_type: str # claim, coverage, policy, general, etc.
|
| 65 |
+
entities: Dict[str, List[str]]
|
| 66 |
+
intent: str
|
| 67 |
+
confidence: float
|
| 68 |
+
keywords: List[str]
|
| 69 |
+
synonyms: List[str]
|
| 70 |
+
context: Dict[str, Any]
|
| 71 |
+
timestamp: datetime
|
| 72 |
+
|
| 73 |
+
@dataclass
|
| 74 |
+
class QueryEntity:
|
| 75 |
+
"""Represents an extracted entity from a query"""
|
| 76 |
+
text: str
|
| 77 |
+
entity_type: str
|
| 78 |
+
confidence: float
|
| 79 |
+
start_pos: int
|
| 80 |
+
end_pos: int
|
| 81 |
+
|
| 82 |
+
class AdvancedQueryParser:
|
| 83 |
+
"""Advanced query parser with entity extraction and query enhancement"""
|
| 84 |
+
|
| 85 |
+
def __init__(self,
|
| 86 |
+
spacy_model: str = "en_core_web_sm",
|
| 87 |
+
use_gpu: bool = True):
|
| 88 |
+
|
| 89 |
+
self.use_gpu = use_gpu
|
| 90 |
+
self.spacy_model = spacy_model
|
| 91 |
+
|
| 92 |
+
# Initialize NLP components
|
| 93 |
+
self._initialize_nlp_components()
|
| 94 |
+
|
| 95 |
+
# Initialize entity extractors
|
| 96 |
+
self._initialize_entity_extractors()
|
| 97 |
+
|
| 98 |
+
# Initialize query enhancement
|
| 99 |
+
self._initialize_query_enhancement()
|
| 100 |
+
|
| 101 |
+
logger.info("Advanced Query Parser initialized")
|
| 102 |
+
|
| 103 |
+
def _initialize_nlp_components(self):
|
| 104 |
+
"""Initialize NLP components"""
|
| 105 |
+
try:
|
| 106 |
+
# Load spaCy model
|
| 107 |
+
# self.nlp = spacy.load(self.spacy_model) # Temporarily commented out due to installation issues
|
| 108 |
+
self.nlp = None # Set to None temporarily
|
| 109 |
+
|
| 110 |
+
# Initialize NLTK components
|
| 111 |
+
self.lemmatizer = WordNetLemmatizer()
|
| 112 |
+
self.stop_words = set(stopwords.words('english'))
|
| 113 |
+
|
| 114 |
+
# Add custom stop words for insurance domain
|
| 115 |
+
insurance_stop_words = {
|
| 116 |
+
'policy', 'claim', 'coverage', 'insurance', 'document',
|
| 117 |
+
'please', 'help', 'need', 'want', 'know', 'tell'
|
| 118 |
+
}
|
| 119 |
+
self.stop_words.update(insurance_stop_words)
|
| 120 |
+
|
| 121 |
+
logger.info("NLP components initialized")
|
| 122 |
+
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logger.error(f"Error initializing NLP components: {e}")
|
| 125 |
+
raise
|
| 126 |
+
|
| 127 |
+
def _initialize_entity_extractors(self):
|
| 128 |
+
"""Initialize entity extraction components"""
|
| 129 |
+
try:
|
| 130 |
+
# Initialize NER pipeline if transformers is available
|
| 131 |
+
if TRANSFORMERS_AVAILABLE:
|
| 132 |
+
try:
|
| 133 |
+
device = 0 if self.use_gpu else -1
|
| 134 |
+
self.ner_pipeline = pipeline(
|
| 135 |
+
"ner",
|
| 136 |
+
model="dbmdz/bert-large-cased-finetuned-conll03-english",
|
| 137 |
+
device=device
|
| 138 |
+
)
|
| 139 |
+
logger.info("NER pipeline initialized")
|
| 140 |
+
except Exception as e:
|
| 141 |
+
logger.warning(f"NER pipeline initialization failed: {e}")
|
| 142 |
+
self.ner_pipeline = None
|
| 143 |
+
else:
|
| 144 |
+
self.ner_pipeline = None
|
| 145 |
+
logger.info("NER pipeline not available (transformers not installed)")
|
| 146 |
+
|
| 147 |
+
# Insurance-specific entity patterns (always available)
|
| 148 |
+
self.insurance_entities = {
|
| 149 |
+
'medical_condition': [
|
| 150 |
+
r'\b(heart attack|stroke|cancer|diabetes|hypertension|asthma|arthritis)\b',
|
| 151 |
+
r'\b(surgery|operation|procedure|treatment|therapy)\b',
|
| 152 |
+
r'\b(medication|prescription|drug|medicine)\b'
|
| 153 |
+
],
|
| 154 |
+
'coverage_type': [
|
| 155 |
+
r'\b(health|medical|dental|vision|life|auto|home|property)\s+(insurance|coverage|policy)\b',
|
| 156 |
+
r'\b(accident|disability|liability|comprehensive|collision)\b'
|
| 157 |
+
],
|
| 158 |
+
'amount': [
|
| 159 |
+
r'\$\d+(?:,\d{3})*(?:\.\d{2})?',
|
| 160 |
+
r'\b\d+\s*(?:dollars?|rupees?|euros?)\b',
|
| 161 |
+
r'\b(?:maximum|minimum|total|sum)\s+(?:of\s+)?\$\d+\b'
|
| 162 |
+
],
|
| 163 |
+
'time_period': [
|
| 164 |
+
r'\b(waiting period|grace period|coverage period|policy term)\b',
|
| 165 |
+
r'\b(\d+\s+(?:days?|weeks?|months?|years?))\b',
|
| 166 |
+
r'\b(immediate|urgent|emergency|routine)\b'
|
| 167 |
+
],
|
| 168 |
+
'document_type': [
|
| 169 |
+
r'\b(claim form|medical certificate|prescription|bill|receipt|invoice)\b',
|
| 170 |
+
r'\b(doctor|physician|specialist|hospital|clinic)\s+(?:report|note|letter)\b'
|
| 171 |
+
]
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
logger.info("Entity extractors initialized")
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
logger.error(f"Error initializing entity extractors: {e}")
|
| 178 |
+
# Set defaults if initialization fails
|
| 179 |
+
self.ner_pipeline = None
|
| 180 |
+
self.insurance_entities = {}
|
| 181 |
+
|
| 182 |
+
def _initialize_query_enhancement(self):
|
| 183 |
+
"""Initialize query enhancement components"""
|
| 184 |
+
try:
|
| 185 |
+
# Query enhancement patterns
|
| 186 |
+
self.enhancement_patterns = {
|
| 187 |
+
'claim_related': {
|
| 188 |
+
'keywords': ['claim', 'file', 'submit', 'process', 'approve', 'reject'],
|
| 189 |
+
'synonyms': ['application', 'request', 'petition', 'appeal'],
|
| 190 |
+
'context': 'claim_processing'
|
| 191 |
+
},
|
| 192 |
+
'coverage_related': {
|
| 193 |
+
'keywords': ['cover', 'include', 'exclude', 'limit', 'maximum', 'minimum'],
|
| 194 |
+
'synonyms': ['protection', 'benefit', 'entitlement', 'eligibility'],
|
| 195 |
+
'context': 'coverage_analysis'
|
| 196 |
+
},
|
| 197 |
+
'policy_related': {
|
| 198 |
+
'keywords': ['policy', 'terms', 'conditions', 'clause', 'section'],
|
| 199 |
+
'synonyms': ['agreement', 'contract', 'document', 'provision'],
|
| 200 |
+
'context': 'policy_review'
|
| 201 |
+
},
|
| 202 |
+
'medical_related': {
|
| 203 |
+
'keywords': ['medical', 'health', 'treatment', 'surgery', 'medication'],
|
| 204 |
+
'synonyms': ['healthcare', 'therapeutic', 'clinical', 'pharmaceutical'],
|
| 205 |
+
'context': 'medical_coverage'
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
# Query type classification patterns
|
| 210 |
+
self.query_types = {
|
| 211 |
+
'claim_inquiry': [
|
| 212 |
+
r'\b(how|what|can|is|does)\s+(?:to\s+)?(?:file|submit|process|claim)\b',
|
| 213 |
+
r'\b(claim|file|submit|process)\s+(?:a\s+)?(?:claim|request)\b'
|
| 214 |
+
],
|
| 215 |
+
'coverage_check': [
|
| 216 |
+
r'\b(cover|include|exclude|limit|maximum|minimum)\b',
|
| 217 |
+
r'\b(is|does|can)\s+(?:.*?)\s+(?:cover|include|exclude)\b',
|
| 218 |
+
r'\b(waiting\s+period|grace\s+period|coverage\s+period)\b',
|
| 219 |
+
r'\b(what\'s|what\s+is)\s+(?:the\s+)?(?:waiting|grace|coverage)\b'
|
| 220 |
+
],
|
| 221 |
+
'policy_review': [
|
| 222 |
+
r'\b(policy|terms|conditions|clause|section)\b',
|
| 223 |
+
r'\b(what|which|where)\s+(?:in\s+)?(?:policy|document)\b'
|
| 224 |
+
],
|
| 225 |
+
'medical_coverage': [
|
| 226 |
+
r'\b(medical|health|treatment|surgery|medication|prescription)\b',
|
| 227 |
+
r'\b(doctor|hospital|clinic|physician|specialist)\b',
|
| 228 |
+
r'\b(dental|dental\s+procedures|dental\s+treatment)\b',
|
| 229 |
+
r'\b(heart\s+surgery|cardiac|surgical)\b'
|
| 230 |
+
],
|
| 231 |
+
'general_inquiry': [
|
| 232 |
+
r'\b(what|how|when|where|why|who)\b',
|
| 233 |
+
r'\b(explain|describe|tell|show)\b'
|
| 234 |
+
]
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
logger.info("Query enhancement components initialized")
|
| 238 |
+
|
| 239 |
+
except Exception as e:
|
| 240 |
+
logger.error(f"Error initializing query enhancement: {e}")
|
| 241 |
+
|
| 242 |
+
def parse_query(self, query: str) -> ParsedQuery:
|
| 243 |
+
"""Main method to parse and enhance a query"""
|
| 244 |
+
try:
|
| 245 |
+
# Clean and preprocess query
|
| 246 |
+
cleaned_query = self._preprocess_query(query)
|
| 247 |
+
|
| 248 |
+
# Extract entities
|
| 249 |
+
entities = self._extract_entities(cleaned_query)
|
| 250 |
+
|
| 251 |
+
# Determine query type and intent
|
| 252 |
+
query_type, intent, confidence = self._classify_query(cleaned_query)
|
| 253 |
+
|
| 254 |
+
# Extract keywords
|
| 255 |
+
keywords = self._extract_keywords(cleaned_query)
|
| 256 |
+
|
| 257 |
+
# Generate synonyms
|
| 258 |
+
synonyms = self._generate_synonyms(keywords)
|
| 259 |
+
|
| 260 |
+
# Enhance query
|
| 261 |
+
enhanced_query = self._enhance_query(cleaned_query, entities, query_type)
|
| 262 |
+
|
| 263 |
+
# Build context
|
| 264 |
+
context = self._build_context(cleaned_query, entities, query_type)
|
| 265 |
+
|
| 266 |
+
parsed_query = ParsedQuery(
|
| 267 |
+
original_query=query,
|
| 268 |
+
enhanced_query=enhanced_query,
|
| 269 |
+
query_type=query_type,
|
| 270 |
+
entities=entities,
|
| 271 |
+
intent=intent,
|
| 272 |
+
confidence=confidence,
|
| 273 |
+
keywords=keywords,
|
| 274 |
+
synonyms=synonyms,
|
| 275 |
+
context=context,
|
| 276 |
+
timestamp=datetime.now()
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
logger.info(f"Query parsed successfully: {query_type} ({confidence:.2f})")
|
| 280 |
+
return parsed_query
|
| 281 |
+
|
| 282 |
+
except Exception as e:
|
| 283 |
+
logger.error(f"Error parsing query: {e}")
|
| 284 |
+
# Return a basic parsed query
|
| 285 |
+
return self._create_basic_parsed_query(query)
|
| 286 |
+
|
| 287 |
+
def _preprocess_query(self, query: str) -> str:
|
| 288 |
+
"""Preprocess and clean the query"""
|
| 289 |
+
try:
|
| 290 |
+
# Convert to lowercase
|
| 291 |
+
query = query.lower().strip()
|
| 292 |
+
|
| 293 |
+
# Remove extra whitespace
|
| 294 |
+
query = re.sub(r'\s+', ' ', query)
|
| 295 |
+
|
| 296 |
+
# Remove special characters but keep important ones
|
| 297 |
+
query = re.sub(r'[^\w\s\-\.\,\?\&]', '', query)
|
| 298 |
+
|
| 299 |
+
# Fix common abbreviations
|
| 300 |
+
query = self._fix_abbreviations(query)
|
| 301 |
+
|
| 302 |
+
return query
|
| 303 |
+
|
| 304 |
+
except Exception as e:
|
| 305 |
+
logger.error(f"Error preprocessing query: {e}")
|
| 306 |
+
return query
|
| 307 |
+
|
| 308 |
+
def _fix_abbreviations(self, query: str) -> str:
|
| 309 |
+
"""Fix common abbreviations in insurance queries"""
|
| 310 |
+
abbreviations = {
|
| 311 |
+
'dr.': 'doctor',
|
| 312 |
+
'doc.': 'document',
|
| 313 |
+
'med.': 'medical',
|
| 314 |
+
'rx': 'prescription',
|
| 315 |
+
'hosp.': 'hospital',
|
| 316 |
+
'clinic.': 'clinic',
|
| 317 |
+
'ins.': 'insurance',
|
| 318 |
+
'pol.': 'policy',
|
| 319 |
+
'claim.': 'claim',
|
| 320 |
+
'coverage.': 'coverage'
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
for abbr, full in abbreviations.items():
|
| 324 |
+
query = query.replace(abbr, full)
|
| 325 |
+
|
| 326 |
+
return query
|
| 327 |
+
|
| 328 |
+
def _extract_entities(self, query: str) -> Dict[str, List[str]]:
|
| 329 |
+
"""Extract entities from the query"""
|
| 330 |
+
entities = {}
|
| 331 |
+
|
| 332 |
+
try:
|
| 333 |
+
# Use spaCy for basic NER
|
| 334 |
+
# doc = self.nlp(query) # Temporarily commented out due to installation issues
|
| 335 |
+
|
| 336 |
+
# Extract named entities
|
| 337 |
+
# for ent in doc.ents: # Temporarily commented out due to installation issues
|
| 338 |
+
# entity_type = ent.label_.lower() # Temporarily commented out due to installation issues
|
| 339 |
+
# if entity_type not in entities: # Temporarily commented out due to installation issues
|
| 340 |
+
# entities[entity_type] = [] # Temporarily commented out due to installation issues
|
| 341 |
+
# entities[entity_type].append(ent.text) # Temporarily commented out due to installation issues
|
| 342 |
+
|
| 343 |
+
# Extract insurance-specific entities using patterns
|
| 344 |
+
for entity_type, patterns in self.insurance_entities.items():
|
| 345 |
+
entities[entity_type] = []
|
| 346 |
+
for pattern in patterns:
|
| 347 |
+
matches = re.findall(pattern, query, re.IGNORECASE)
|
| 348 |
+
entities[entity_type].extend(matches)
|
| 349 |
+
|
| 350 |
+
# Use BERT NER if available
|
| 351 |
+
if hasattr(self, 'ner_pipeline') and self.ner_pipeline is not None:
|
| 352 |
+
try:
|
| 353 |
+
ner_results = self.ner_pipeline(query)
|
| 354 |
+
for result in ner_results:
|
| 355 |
+
entity_type = result['entity'].lower()
|
| 356 |
+
if entity_type not in entities:
|
| 357 |
+
entities[entity_type] = []
|
| 358 |
+
entities[entity_type].append(result['word'])
|
| 359 |
+
except Exception as e:
|
| 360 |
+
logger.debug(f"BERT NER failed: {e}")
|
| 361 |
+
|
| 362 |
+
# Remove duplicates
|
| 363 |
+
for entity_type in entities:
|
| 364 |
+
entities[entity_type] = list(set(entities[entity_type]))
|
| 365 |
+
|
| 366 |
+
return entities
|
| 367 |
+
|
| 368 |
+
except Exception as e:
|
| 369 |
+
logger.error(f"Error extracting entities: {e}")
|
| 370 |
+
return {}
|
| 371 |
+
|
| 372 |
+
def _classify_query(self, query: str) -> Tuple[str, str, float]:
|
| 373 |
+
"""Classify the query type and determine intent"""
|
| 374 |
+
try:
|
| 375 |
+
best_type = 'general_inquiry'
|
| 376 |
+
best_confidence = 0.0
|
| 377 |
+
intent = 'information_seeking'
|
| 378 |
+
|
| 379 |
+
# Check each query type
|
| 380 |
+
for query_type, patterns in self.query_types.items():
|
| 381 |
+
confidence = 0.0
|
| 382 |
+
matches = 0
|
| 383 |
+
|
| 384 |
+
for pattern in patterns:
|
| 385 |
+
if re.search(pattern, query, re.IGNORECASE):
|
| 386 |
+
matches += 1
|
| 387 |
+
|
| 388 |
+
if matches > 0:
|
| 389 |
+
confidence = matches / len(patterns)
|
| 390 |
+
|
| 391 |
+
if confidence > best_confidence:
|
| 392 |
+
best_confidence = confidence
|
| 393 |
+
best_type = query_type
|
| 394 |
+
|
| 395 |
+
# Determine intent based on query type
|
| 396 |
+
intent_mapping = {
|
| 397 |
+
'claim_inquiry': 'claim_processing',
|
| 398 |
+
'coverage_check': 'coverage_analysis',
|
| 399 |
+
'policy_review': 'policy_review',
|
| 400 |
+
'medical_coverage': 'medical_coverage',
|
| 401 |
+
'general_inquiry': 'information_seeking'
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
intent = intent_mapping.get(best_type, 'information_seeking')
|
| 405 |
+
|
| 406 |
+
return best_type, intent, best_confidence
|
| 407 |
+
|
| 408 |
+
except Exception as e:
|
| 409 |
+
logger.error(f"Error classifying query: {e}")
|
| 410 |
+
return 'general_inquiry', 'information_seeking', 0.0
|
| 411 |
+
|
| 412 |
+
def _extract_keywords(self, query: str) -> List[str]:
|
| 413 |
+
"""Extract important keywords from the query"""
|
| 414 |
+
try:
|
| 415 |
+
# Tokenize with fallback
|
| 416 |
+
try:
|
| 417 |
+
tokens = word_tokenize(query)
|
| 418 |
+
except Exception as tokenize_error:
|
| 419 |
+
logger.warning(f"Word tokenization failed, using simple split: {tokenize_error}")
|
| 420 |
+
tokens = query.split()
|
| 421 |
+
|
| 422 |
+
# Remove stop words and lemmatize
|
| 423 |
+
keywords = []
|
| 424 |
+
for token in tokens:
|
| 425 |
+
if token.lower() not in self.stop_words and len(token) > 2:
|
| 426 |
+
try:
|
| 427 |
+
lemmatized = self.lemmatizer.lemmatize(token.lower())
|
| 428 |
+
keywords.append(lemmatized)
|
| 429 |
+
except Exception as lemmatize_error:
|
| 430 |
+
logger.debug(f"Lemmatization failed for '{token}': {lemmatize_error}")
|
| 431 |
+
keywords.append(token.lower())
|
| 432 |
+
|
| 433 |
+
return keywords
|
| 434 |
+
|
| 435 |
+
except Exception as e:
|
| 436 |
+
logger.error(f"Error extracting keywords: {e}")
|
| 437 |
+
return []
|
| 438 |
+
|
| 439 |
+
def _generate_synonyms(self, keywords: List[str]) -> List[str]:
|
| 440 |
+
"""Generate synonyms for keywords"""
|
| 441 |
+
synonyms = []
|
| 442 |
+
|
| 443 |
+
try:
|
| 444 |
+
# Simple synonym mapping for insurance domain
|
| 445 |
+
synonym_mapping = {
|
| 446 |
+
'claim': ['application', 'request', 'petition'],
|
| 447 |
+
'cover': ['include', 'protect', 'insure'],
|
| 448 |
+
'policy': ['document', 'agreement', 'contract'],
|
| 449 |
+
'medical': ['health', 'clinical', 'therapeutic'],
|
| 450 |
+
'surgery': ['operation', 'procedure', 'treatment'],
|
| 451 |
+
'hospital': ['clinic', 'medical center', 'facility'],
|
| 452 |
+
'doctor': ['physician', 'specialist', 'medical practitioner'],
|
| 453 |
+
'medicine': ['medication', 'drug', 'prescription'],
|
| 454 |
+
'cost': ['expense', 'charge', 'fee', 'amount'],
|
| 455 |
+
'limit': ['maximum', 'cap', 'ceiling', 'restriction']
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
for keyword in keywords:
|
| 459 |
+
if keyword in synonym_mapping:
|
| 460 |
+
synonyms.extend(synonym_mapping[keyword])
|
| 461 |
+
|
| 462 |
+
return list(set(synonyms))
|
| 463 |
+
|
| 464 |
+
except Exception as e:
|
| 465 |
+
logger.error(f"Error generating synonyms: {e}")
|
| 466 |
+
return []
|
| 467 |
+
|
| 468 |
+
def _enhance_query(self, query: str, entities: Dict[str, List[str]], query_type: str) -> str:
|
| 469 |
+
"""Enhance the query with additional context and synonyms"""
|
| 470 |
+
try:
|
| 471 |
+
enhanced_parts = [query]
|
| 472 |
+
|
| 473 |
+
# Add entity context
|
| 474 |
+
for entity_type, entity_list in entities.items():
|
| 475 |
+
if entity_list:
|
| 476 |
+
enhanced_parts.append(f"related to {entity_type}: {', '.join(entity_list)}")
|
| 477 |
+
|
| 478 |
+
# Add query type context
|
| 479 |
+
if query_type in self.enhancement_patterns:
|
| 480 |
+
pattern = self.enhancement_patterns[query_type]
|
| 481 |
+
enhanced_parts.append(f"context: {pattern['context']}")
|
| 482 |
+
|
| 483 |
+
# Add synonyms for important terms
|
| 484 |
+
synonyms = self._generate_synonyms(self._extract_keywords(query))
|
| 485 |
+
if synonyms:
|
| 486 |
+
enhanced_parts.append(f"synonyms: {', '.join(synonyms[:5])}")
|
| 487 |
+
|
| 488 |
+
return " | ".join(enhanced_parts)
|
| 489 |
+
|
| 490 |
+
except Exception as e:
|
| 491 |
+
logger.error(f"Error enhancing query: {e}")
|
| 492 |
+
return query
|
| 493 |
+
|
| 494 |
+
def _build_context(self, query: str, entities: Dict[str, List[str]], query_type: str) -> Dict[str, Any]:
|
| 495 |
+
"""Build context information for the query"""
|
| 496 |
+
try:
|
| 497 |
+
context = {
|
| 498 |
+
'query_length': len(query),
|
| 499 |
+
'has_entities': len(entities) > 0,
|
| 500 |
+
'entity_types': list(entities.keys()),
|
| 501 |
+
'query_type': query_type,
|
| 502 |
+
'is_medical': any('medical' in entity_type for entity_type in entities.keys()),
|
| 503 |
+
'has_amounts': 'amount' in entities,
|
| 504 |
+
'has_time_periods': 'time_period' in entities
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
return context
|
| 508 |
+
|
| 509 |
+
except Exception as e:
|
| 510 |
+
logger.error(f"Error building context: {e}")
|
| 511 |
+
return {}
|
| 512 |
+
|
| 513 |
+
def _create_basic_parsed_query(self, query: str) -> ParsedQuery:
|
| 514 |
+
"""Create a basic parsed query when parsing fails"""
|
| 515 |
+
return ParsedQuery(
|
| 516 |
+
original_query=query,
|
| 517 |
+
enhanced_query=query,
|
| 518 |
+
query_type='general_inquiry',
|
| 519 |
+
entities={},
|
| 520 |
+
intent='information_seeking',
|
| 521 |
+
confidence=0.0,
|
| 522 |
+
keywords=[],
|
| 523 |
+
synonyms=[],
|
| 524 |
+
context={},
|
| 525 |
+
timestamp=datetime.now()
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
def get_query_suggestions(self, query: str) -> List[str]:
|
| 529 |
+
"""Generate query suggestions based on the input"""
|
| 530 |
+
try:
|
| 531 |
+
suggestions = []
|
| 532 |
+
|
| 533 |
+
# Basic suggestions based on query type
|
| 534 |
+
if 'claim' in query.lower():
|
| 535 |
+
suggestions.extend([
|
| 536 |
+
"How do I file a claim?",
|
| 537 |
+
"What documents are needed for claim submission?",
|
| 538 |
+
"What is the claim processing time?",
|
| 539 |
+
"Can I track my claim status?"
|
| 540 |
+
])
|
| 541 |
+
|
| 542 |
+
if 'cover' in query.lower() or 'coverage' in query.lower():
|
| 543 |
+
suggestions.extend([
|
| 544 |
+
"What is covered under this policy?",
|
| 545 |
+
"What are the coverage limits?",
|
| 546 |
+
"Are pre-existing conditions covered?",
|
| 547 |
+
"What is not covered?"
|
| 548 |
+
])
|
| 549 |
+
|
| 550 |
+
if 'medical' in query.lower() or 'health' in query.lower():
|
| 551 |
+
suggestions.extend([
|
| 552 |
+
"What medical procedures are covered?",
|
| 553 |
+
"Are prescription drugs covered?",
|
| 554 |
+
"What is the coverage for hospital stays?",
|
| 555 |
+
"Are specialist consultations covered?"
|
| 556 |
+
])
|
| 557 |
+
|
| 558 |
+
# Add general suggestions if none specific
|
| 559 |
+
if not suggestions:
|
| 560 |
+
suggestions.extend([
|
| 561 |
+
"What is covered under this policy?",
|
| 562 |
+
"How do I file a claim?",
|
| 563 |
+
"What are the policy terms and conditions?",
|
| 564 |
+
"What documents do I need?"
|
| 565 |
+
])
|
| 566 |
+
|
| 567 |
+
return suggestions[:5] # Return top 5 suggestions
|
| 568 |
+
|
| 569 |
+
except Exception as e:
|
| 570 |
+
logger.error(f"Error generating query suggestions: {e}")
|
| 571 |
+
return []
|
| 572 |
+
|
| 573 |
+
# Example usage
|
| 574 |
+
if __name__ == "__main__":
|
| 575 |
+
parser = AdvancedQueryParser()
|
| 576 |
+
|
| 577 |
+
# Test queries
|
| 578 |
+
test_queries = [
|
| 579 |
+
"Is heart surgery covered?",
|
| 580 |
+
"How do I file a claim?",
|
| 581 |
+
"What's the waiting period?",
|
| 582 |
+
"Can I claim for dental treatment?",
|
| 583 |
+
"What documents are needed?"
|
| 584 |
+
]
|
| 585 |
+
|
| 586 |
+
for query in test_queries:
|
| 587 |
+
print(f"\n{'='*50}")
|
| 588 |
+
print(f"Original Query: {query}")
|
| 589 |
+
|
| 590 |
+
parsed = parser.parse_query(query)
|
| 591 |
+
|
| 592 |
+
print(f"Enhanced Query: {parsed.enhanced_query}")
|
| 593 |
+
print(f"Query Type: {parsed.query_type}")
|
| 594 |
+
print(f"Intent: {parsed.intent}")
|
| 595 |
+
print(f"Confidence: {parsed.confidence:.2f}")
|
| 596 |
+
print(f"Entities: {parsed.entities}")
|
| 597 |
+
print(f"Keywords: {parsed.keywords}")
|
| 598 |
+
|
| 599 |
+
suggestions = parser.get_query_suggestions(query)
|
| 600 |
+
print(f"Suggestions: {suggestions[:2]}")
|
quick_integration_test.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Quick Interactive Test for Integrated System
|
| 3 |
+
Test the complete workflow with user input
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
def quick_test():
|
| 7 |
+
"""Quick interactive test of the integrated system"""
|
| 8 |
+
print("🚀 Quick Integration Test")
|
| 9 |
+
print("="*40)
|
| 10 |
+
print("Testing: Query Parser → Vector Database → LLM Reasoning")
|
| 11 |
+
print("="*40)
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
# Import components
|
| 15 |
+
print("🔄 Loading components...")
|
| 16 |
+
from query_parser import AdvancedQueryParser
|
| 17 |
+
from vector_database import VectorDatabase
|
| 18 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 19 |
+
print("✅ Components loaded")
|
| 20 |
+
|
| 21 |
+
# Initialize components
|
| 22 |
+
print("🔄 Initializing...")
|
| 23 |
+
query_parser = AdvancedQueryParser(use_gpu=False)
|
| 24 |
+
vector_db = VectorDatabase(
|
| 25 |
+
collection_name="quick_test",
|
| 26 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 27 |
+
persist_directory="./quick_test_db"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Try to initialize LLM reasoning
|
| 31 |
+
try:
|
| 32 |
+
reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
|
| 33 |
+
llm_available = True
|
| 34 |
+
print("✅ LLM reasoning available")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"⚠️ LLM reasoning not available: {e}")
|
| 37 |
+
llm_available = False
|
| 38 |
+
|
| 39 |
+
# Add sample documents
|
| 40 |
+
print("🔄 Adding sample documents...")
|
| 41 |
+
sample_docs = [
|
| 42 |
+
{
|
| 43 |
+
'content': 'Heart surgery is covered up to $50,000 with 90-day waiting period.',
|
| 44 |
+
'metadata': {'source': 'policy.pdf', 'section': 'coverage'}
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
'content': 'Dental treatment is covered up to $2,000 annually with 6-month waiting period.',
|
| 48 |
+
'metadata': {'source': 'policy.pdf', 'section': 'dental'}
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
'content': 'To file a claim, you need: claim form, medical certificate, receipts, and bills.',
|
| 52 |
+
'metadata': {'source': 'claims.pdf', 'section': 'procedures'}
|
| 53 |
+
}
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
for doc in sample_docs:
|
| 57 |
+
vector_db.add_document(doc['content'], doc['metadata'])
|
| 58 |
+
print(f"✅ Added {len(sample_docs)} documents")
|
| 59 |
+
|
| 60 |
+
# Interactive testing
|
| 61 |
+
print("\n🎯 Interactive Testing")
|
| 62 |
+
print("="*30)
|
| 63 |
+
print("Enter your queries (type 'quit' to exit):")
|
| 64 |
+
|
| 65 |
+
while True:
|
| 66 |
+
try:
|
| 67 |
+
query = input("\n❓ Your query: ").strip()
|
| 68 |
+
|
| 69 |
+
if query.lower() in ['quit', 'exit', 'q']:
|
| 70 |
+
break
|
| 71 |
+
|
| 72 |
+
if not query:
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
print(f"\n🔄 Processing: {query}")
|
| 76 |
+
print("-" * 40)
|
| 77 |
+
|
| 78 |
+
# Step 1: Parse query
|
| 79 |
+
print("📝 Step 1: Parsing query...")
|
| 80 |
+
parsed = query_parser.parse_query(query)
|
| 81 |
+
print(f" Type: {parsed.query_type}")
|
| 82 |
+
print(f" Intent: {parsed.intent}")
|
| 83 |
+
print(f" Confidence: {parsed.confidence:.2f}")
|
| 84 |
+
if parsed.entities:
|
| 85 |
+
print(f" Entities: {list(parsed.entities.keys())}")
|
| 86 |
+
|
| 87 |
+
# Step 2: Search vector database
|
| 88 |
+
print("\n🔍 Step 2: Searching documents...")
|
| 89 |
+
results = vector_db.search_documents(query, n_results=2, similarity_threshold=0.3)
|
| 90 |
+
print(f" Found {len(results)} relevant documents")
|
| 91 |
+
|
| 92 |
+
for i, result in enumerate(results, 1):
|
| 93 |
+
print(f" {i}. Similarity: {result.get('similarity_score', 0):.2f}")
|
| 94 |
+
print(f" Source: {result.get('source_file', 'Unknown')}")
|
| 95 |
+
print(f" Content: {result.get('content', '')[:100]}...")
|
| 96 |
+
|
| 97 |
+
# Step 3: LLM reasoning
|
| 98 |
+
if llm_available and results:
|
| 99 |
+
print("\n🧠 Step 3: LLM reasoning...")
|
| 100 |
+
reasoning_result = reasoning_engine.analyze_query(
|
| 101 |
+
query=query,
|
| 102 |
+
context=results,
|
| 103 |
+
query_type=parsed.query_type
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
print(f" Decision: {reasoning_result.decision.upper()}")
|
| 107 |
+
print(f" Confidence: {reasoning_result.confidence_score:.2f}")
|
| 108 |
+
print(f" Justification: {reasoning_result.justification[:150]}...")
|
| 109 |
+
|
| 110 |
+
if reasoning_result.amount:
|
| 111 |
+
print(f" Amount: ${reasoning_result.amount:,.2f}")
|
| 112 |
+
if reasoning_result.waiting_period:
|
| 113 |
+
print(f" Waiting Period: {reasoning_result.waiting_period}")
|
| 114 |
+
|
| 115 |
+
# Show explanation
|
| 116 |
+
print(f"\n📋 Explanation:")
|
| 117 |
+
explanation = reasoning_engine.explain_decision(reasoning_result)
|
| 118 |
+
print(explanation)
|
| 119 |
+
|
| 120 |
+
else:
|
| 121 |
+
print("\n🧠 Step 3: LLM reasoning (not available)")
|
| 122 |
+
print(" Query parsing and document search completed successfully")
|
| 123 |
+
|
| 124 |
+
print("\n" + "="*50)
|
| 125 |
+
|
| 126 |
+
except KeyboardInterrupt:
|
| 127 |
+
print("\n\n👋 Goodbye!")
|
| 128 |
+
break
|
| 129 |
+
except Exception as e:
|
| 130 |
+
print(f"\n❌ Error processing query: {e}")
|
| 131 |
+
|
| 132 |
+
# Cleanup
|
| 133 |
+
print("\n🧹 Cleaning up...")
|
| 134 |
+
import shutil
|
| 135 |
+
if os.path.exists("./quick_test_db"):
|
| 136 |
+
shutil.rmtree("./quick_test_db")
|
| 137 |
+
print("✅ Cleanup completed")
|
| 138 |
+
|
| 139 |
+
print("\n🎉 Quick test completed!")
|
| 140 |
+
|
| 141 |
+
except Exception as e:
|
| 142 |
+
print(f"❌ Quick test failed: {e}")
|
| 143 |
+
import traceback
|
| 144 |
+
traceback.print_exc()
|
| 145 |
+
|
| 146 |
+
def test_specific_query(query_text):
|
| 147 |
+
"""Test a specific query"""
|
| 148 |
+
print(f"🧪 Testing specific query: {query_text}")
|
| 149 |
+
print("="*50)
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
from query_parser import AdvancedQueryParser
|
| 153 |
+
from vector_database import VectorDatabase
|
| 154 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 155 |
+
|
| 156 |
+
# Initialize
|
| 157 |
+
query_parser = AdvancedQueryParser(use_gpu=False)
|
| 158 |
+
vector_db = VectorDatabase(
|
| 159 |
+
collection_name="specific_test",
|
| 160 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 161 |
+
persist_directory="./specific_test_db"
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# Add test document
|
| 165 |
+
vector_db.add_document(
|
| 166 |
+
"Heart surgery is covered up to $50,000 with 90-day waiting period.",
|
| 167 |
+
{'source': 'test.pdf', 'type': 'coverage'}
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
# Process query
|
| 171 |
+
parsed = query_parser.parse_query(query_text)
|
| 172 |
+
results = vector_db.search_documents(query_text, n_results=1)
|
| 173 |
+
|
| 174 |
+
print(f"Query Type: {parsed.query_type}")
|
| 175 |
+
print(f"Confidence: {parsed.confidence:.2f}")
|
| 176 |
+
print(f"Search Results: {len(results)}")
|
| 177 |
+
|
| 178 |
+
if results:
|
| 179 |
+
print(f"Best Match: {results[0].get('content', '')[:100]}...")
|
| 180 |
+
|
| 181 |
+
# Try LLM reasoning
|
| 182 |
+
try:
|
| 183 |
+
reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
|
| 184 |
+
reasoning_result = reasoning_engine.analyze_query(
|
| 185 |
+
query_text, results, parsed.query_type
|
| 186 |
+
)
|
| 187 |
+
print(f"LLM Decision: {reasoning_result.decision}")
|
| 188 |
+
print(f"LLM Confidence: {reasoning_result.confidence_score:.2f}")
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"LLM Reasoning failed: {e}")
|
| 191 |
+
|
| 192 |
+
# Cleanup
|
| 193 |
+
import shutil
|
| 194 |
+
if os.path.exists("./specific_test_db"):
|
| 195 |
+
shutil.rmtree("./specific_test_db")
|
| 196 |
+
|
| 197 |
+
except Exception as e:
|
| 198 |
+
print(f"❌ Test failed: {e}")
|
| 199 |
+
|
| 200 |
+
if __name__ == "__main__":
|
| 201 |
+
import os
|
| 202 |
+
import sys
|
| 203 |
+
|
| 204 |
+
# Check if specific query provided
|
| 205 |
+
if len(sys.argv) > 1:
|
| 206 |
+
query = " ".join(sys.argv[1:])
|
| 207 |
+
test_specific_query(query)
|
| 208 |
+
else:
|
| 209 |
+
quick_test()
|
quick_test.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Quick Test for app2.py Server
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import requests
|
| 7 |
+
import time
|
| 8 |
+
|
| 9 |
+
BASE_URL = "https://0468c638cef7.ngrok-free.app"
|
| 10 |
+
|
| 11 |
+
def test_server():
|
| 12 |
+
print("🧪 Quick Server Test")
|
| 13 |
+
print("=" * 40)
|
| 14 |
+
|
| 15 |
+
# Test 1: Health Check
|
| 16 |
+
print("1. Testing health endpoint...")
|
| 17 |
+
try:
|
| 18 |
+
response = requests.get(f"{BASE_URL}/api/health", timeout=10)
|
| 19 |
+
print(f" Status: {response.status_code}")
|
| 20 |
+
if response.status_code == 200:
|
| 21 |
+
print(" ✅ Server is running!")
|
| 22 |
+
else:
|
| 23 |
+
print(f" ❌ Server error: {response.text}")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f" ❌ Connection failed: {e}")
|
| 26 |
+
print(" 💡 Make sure to run: python app2.py")
|
| 27 |
+
return False
|
| 28 |
+
|
| 29 |
+
# Test 2: Simple Query
|
| 30 |
+
print("\n2. Testing simple query...")
|
| 31 |
+
try:
|
| 32 |
+
headers = {
|
| 33 |
+
"Content-Type": "application/json",
|
| 34 |
+
"Authorization": "Bearer test_key_123"
|
| 35 |
+
}
|
| 36 |
+
payload = {
|
| 37 |
+
"questions": ["What is the grace period for premium payment?"]
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
response = requests.post(f"{BASE_URL}/hackrx/run",
|
| 41 |
+
json=payload, headers=headers, timeout=30)
|
| 42 |
+
print(f" Status: {response.status_code}")
|
| 43 |
+
if response.status_code == 200:
|
| 44 |
+
print(" ✅ Query processed successfully!")
|
| 45 |
+
result = response.json()
|
| 46 |
+
print(f" Answer: {result.get('answers', [''])[0][:100]}...")
|
| 47 |
+
else:
|
| 48 |
+
print(f" ❌ Query failed: {response.text}")
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f" ❌ Query error: {e}")
|
| 51 |
+
|
| 52 |
+
print("\n" + "=" * 40)
|
| 53 |
+
print("📋 NEXT STEPS:")
|
| 54 |
+
print("1. Make sure app2.py is running: python app2.py")
|
| 55 |
+
print("2. Keep the server running in a separate terminal")
|
| 56 |
+
print("3. Test your Postman requests")
|
| 57 |
+
print("=" * 40)
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
test_server()
|
rag_system.py
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main RAG System - Orchestrates All Components
|
| 3 |
+
Integrates document processing, vector database, query parsing, and LLM reasoning
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import time
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 12 |
+
from dataclasses import dataclass, asdict
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
# Import our custom components
|
| 16 |
+
from document_processer import AdvancedDocumentProcessor, DocumentChunk
|
| 17 |
+
from vector_database import VectorDatabase, SearchResult
|
| 18 |
+
from query_parser import AdvancedQueryParser, ParsedQuery
|
| 19 |
+
from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
|
| 20 |
+
|
| 21 |
+
# Configure logging
|
| 22 |
+
logging.basicConfig(level=logging.INFO)
|
| 23 |
+
logger = logging.getLogger(__name__)
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class QueryResult:
|
| 27 |
+
"""Represents the complete result of a query processing"""
|
| 28 |
+
query: str
|
| 29 |
+
parsed_query: ParsedQuery
|
| 30 |
+
search_results: List[SearchResult]
|
| 31 |
+
reasoning_result: ReasoningResult
|
| 32 |
+
processing_time: float
|
| 33 |
+
timestamp: datetime
|
| 34 |
+
audit_trail: Dict[str, Any]
|
| 35 |
+
|
| 36 |
+
class AdvancedRAGSystem:
|
| 37 |
+
"""Advanced RAG system that orchestrates all components"""
|
| 38 |
+
|
| 39 |
+
def __init__(self,
|
| 40 |
+
model_path: str = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
|
| 41 |
+
use_gpu: bool = True,
|
| 42 |
+
vector_db_path: str = "./vector_db"):
|
| 43 |
+
|
| 44 |
+
self.model_path = model_path
|
| 45 |
+
self.use_gpu = use_gpu
|
| 46 |
+
self.vector_db_path = vector_db_path
|
| 47 |
+
|
| 48 |
+
# Initialize components
|
| 49 |
+
self._initialize_components()
|
| 50 |
+
|
| 51 |
+
# Audit trail storage
|
| 52 |
+
self.audit_log = []
|
| 53 |
+
|
| 54 |
+
logger.info("Advanced RAG System initialized successfully")
|
| 55 |
+
|
| 56 |
+
def _initialize_components(self):
|
| 57 |
+
"""Initialize all system components"""
|
| 58 |
+
try:
|
| 59 |
+
# Initialize document processor
|
| 60 |
+
self.document_processor = AdvancedDocumentProcessor(
|
| 61 |
+
ocr_language='eng',
|
| 62 |
+
chunk_size=1000,
|
| 63 |
+
chunk_overlap=200
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# Initialize vector database
|
| 67 |
+
self.vector_database = VectorDatabase(
|
| 68 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 69 |
+
collection_name="documents",
|
| 70 |
+
persist_directory=self.vector_db_path,
|
| 71 |
+
use_gpu=self.use_gpu
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
# Initialize query parser (using NLTK instead of spaCy)
|
| 75 |
+
self.query_parser = AdvancedQueryParser(
|
| 76 |
+
use_gpu=self.use_gpu
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# Initialize LLM reasoning engine
|
| 80 |
+
self.reasoning_engine = AdvancedLLMReasoning(
|
| 81 |
+
model_path=self.model_path,
|
| 82 |
+
use_gpu=self.use_gpu,
|
| 83 |
+
max_tokens=2048
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
logger.info("All components initialized successfully")
|
| 87 |
+
|
| 88 |
+
except Exception as e:
|
| 89 |
+
logger.error(f"Error initializing components: {e}")
|
| 90 |
+
raise
|
| 91 |
+
|
| 92 |
+
def ingest_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
|
| 93 |
+
"""Ingest and process a document"""
|
| 94 |
+
try:
|
| 95 |
+
logger.info(f"Starting document ingestion: {file_path}")
|
| 96 |
+
|
| 97 |
+
# Process document
|
| 98 |
+
chunks = self.document_processor.process_document(file_path, use_ocr)
|
| 99 |
+
logger.info(f"Document processor created {len(chunks)} chunks")
|
| 100 |
+
|
| 101 |
+
if not chunks:
|
| 102 |
+
logger.warning("No chunks created by document processor")
|
| 103 |
+
return []
|
| 104 |
+
|
| 105 |
+
# Add to vector database
|
| 106 |
+
logger.info(f"Adding {len(chunks)} chunks to vector database...")
|
| 107 |
+
success = self.vector_database.add_documents(chunks)
|
| 108 |
+
logger.info(f"Vector database add_documents returned: {success}")
|
| 109 |
+
|
| 110 |
+
if success:
|
| 111 |
+
logger.info(f"Successfully ingested {len(chunks)} chunks from {file_path}")
|
| 112 |
+
|
| 113 |
+
# Add to audit trail
|
| 114 |
+
self._add_audit_entry({
|
| 115 |
+
'action': 'document_ingestion',
|
| 116 |
+
'file_path': file_path,
|
| 117 |
+
'chunks_processed': len(chunks),
|
| 118 |
+
'use_ocr': use_ocr,
|
| 119 |
+
'timestamp': datetime.now().isoformat(),
|
| 120 |
+
'status': 'success'
|
| 121 |
+
})
|
| 122 |
+
|
| 123 |
+
return chunks
|
| 124 |
+
else:
|
| 125 |
+
logger.error(f"Failed to add documents to vector database, but returning chunks anyway")
|
| 126 |
+
# Return chunks even if vector database fails, so the user can still see the processing worked
|
| 127 |
+
return chunks
|
| 128 |
+
|
| 129 |
+
except Exception as e:
|
| 130 |
+
logger.error(f"Error ingesting document {file_path}: {e}")
|
| 131 |
+
|
| 132 |
+
# Add error to audit trail
|
| 133 |
+
self._add_audit_entry({
|
| 134 |
+
'action': 'document_ingestion',
|
| 135 |
+
'file_path': file_path,
|
| 136 |
+
'error': str(e),
|
| 137 |
+
'timestamp': datetime.now().isoformat(),
|
| 138 |
+
'status': 'error'
|
| 139 |
+
})
|
| 140 |
+
|
| 141 |
+
raise
|
| 142 |
+
|
| 143 |
+
def process_query(self, query: str, n_results: int = 5) -> QueryResult:
|
| 144 |
+
"""Process a natural language query"""
|
| 145 |
+
try:
|
| 146 |
+
start_time = time.time()
|
| 147 |
+
|
| 148 |
+
logger.info(f"Processing query: {query}")
|
| 149 |
+
|
| 150 |
+
# Step 1: Parse the query
|
| 151 |
+
parsed_query = self.query_parser.parse_query(query)
|
| 152 |
+
|
| 153 |
+
# Step 2: Search for relevant documents
|
| 154 |
+
search_results = self.vector_database.hybrid_search(
|
| 155 |
+
query=parsed_query.enhanced_query,
|
| 156 |
+
n_results=n_results,
|
| 157 |
+
semantic_weight=0.7,
|
| 158 |
+
keyword_weight=0.3
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Step 3: Prepare context for reasoning
|
| 162 |
+
context = self._prepare_context_for_reasoning(search_results)
|
| 163 |
+
|
| 164 |
+
# Step 4: Analyze with LLM reasoning
|
| 165 |
+
reasoning_result = self.reasoning_engine.analyze_query(
|
| 166 |
+
query=query,
|
| 167 |
+
context=context,
|
| 168 |
+
query_type=parsed_query.query_type
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
processing_time = time.time() - start_time
|
| 172 |
+
|
| 173 |
+
# Step 5: Create audit trail
|
| 174 |
+
audit_trail = self._create_audit_trail(
|
| 175 |
+
query, parsed_query, search_results, reasoning_result, processing_time
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
# Step 6: Build result
|
| 179 |
+
result = QueryResult(
|
| 180 |
+
query=query,
|
| 181 |
+
parsed_query=parsed_query,
|
| 182 |
+
search_results=search_results,
|
| 183 |
+
reasoning_result=reasoning_result,
|
| 184 |
+
processing_time=processing_time,
|
| 185 |
+
timestamp=datetime.now(),
|
| 186 |
+
audit_trail=audit_trail
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
# Add to audit log
|
| 190 |
+
self._add_audit_entry(audit_trail)
|
| 191 |
+
|
| 192 |
+
logger.info(f"Query processed successfully in {processing_time:.2f}s")
|
| 193 |
+
return result
|
| 194 |
+
|
| 195 |
+
except Exception as e:
|
| 196 |
+
logger.error(f"Error processing query: {e}")
|
| 197 |
+
|
| 198 |
+
# Create fallback result
|
| 199 |
+
return self._create_fallback_result(query, str(e))
|
| 200 |
+
|
| 201 |
+
def _prepare_context_for_reasoning(self, search_results: List[SearchResult]) -> List[Dict[str, Any]]:
|
| 202 |
+
"""Prepare search results for LLM reasoning"""
|
| 203 |
+
try:
|
| 204 |
+
context = []
|
| 205 |
+
|
| 206 |
+
for result in search_results:
|
| 207 |
+
context_item = {
|
| 208 |
+
'content': result.content,
|
| 209 |
+
'source_file': result.source_file,
|
| 210 |
+
'similarity_score': result.similarity_score,
|
| 211 |
+
'section_type': result.section_type,
|
| 212 |
+
'metadata': result.metadata
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
# Add table data if present
|
| 216 |
+
if result.table_data:
|
| 217 |
+
context_item['table_data'] = result.table_data
|
| 218 |
+
|
| 219 |
+
context.append(context_item)
|
| 220 |
+
|
| 221 |
+
return context
|
| 222 |
+
|
| 223 |
+
except Exception as e:
|
| 224 |
+
logger.error(f"Error preparing context: {e}")
|
| 225 |
+
return []
|
| 226 |
+
|
| 227 |
+
def _create_audit_trail(self,
|
| 228 |
+
query: str,
|
| 229 |
+
parsed_query: ParsedQuery,
|
| 230 |
+
search_results: List[SearchResult],
|
| 231 |
+
reasoning_result: ReasoningResult,
|
| 232 |
+
processing_time: float) -> Dict[str, Any]:
|
| 233 |
+
"""Create comprehensive audit trail"""
|
| 234 |
+
try:
|
| 235 |
+
audit_trail = {
|
| 236 |
+
'action': 'query_processing',
|
| 237 |
+
'query': query,
|
| 238 |
+
'parsed_query': {
|
| 239 |
+
'query_type': parsed_query.query_type,
|
| 240 |
+
'intent': parsed_query.intent,
|
| 241 |
+
'confidence': parsed_query.confidence,
|
| 242 |
+
'entities': parsed_query.entities,
|
| 243 |
+
'keywords': parsed_query.keywords
|
| 244 |
+
},
|
| 245 |
+
'search_results': {
|
| 246 |
+
'count': len(search_results),
|
| 247 |
+
'top_results': [
|
| 248 |
+
{
|
| 249 |
+
'content_preview': result.content[:100] + "...",
|
| 250 |
+
'source_file': result.source_file,
|
| 251 |
+
'similarity_score': result.similarity_score,
|
| 252 |
+
'section_type': result.section_type
|
| 253 |
+
}
|
| 254 |
+
for result in search_results[:3]
|
| 255 |
+
]
|
| 256 |
+
},
|
| 257 |
+
'reasoning_result': {
|
| 258 |
+
'decision': reasoning_result.decision,
|
| 259 |
+
'confidence_score': reasoning_result.confidence_score,
|
| 260 |
+
'relevant_clauses': reasoning_result.relevant_clauses,
|
| 261 |
+
'amount': reasoning_result.amount,
|
| 262 |
+
'waiting_period': reasoning_result.waiting_period
|
| 263 |
+
},
|
| 264 |
+
'processing_time': processing_time,
|
| 265 |
+
'timestamp': datetime.now().isoformat(),
|
| 266 |
+
'status': 'success'
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
return audit_trail
|
| 270 |
+
|
| 271 |
+
except Exception as e:
|
| 272 |
+
logger.error(f"Error creating audit trail: {e}")
|
| 273 |
+
return {
|
| 274 |
+
'action': 'query_processing',
|
| 275 |
+
'query': query,
|
| 276 |
+
'error': str(e),
|
| 277 |
+
'timestamp': datetime.now().isoformat(),
|
| 278 |
+
'status': 'error'
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
def _create_fallback_result(self, query: str, error: str) -> QueryResult:
|
| 282 |
+
"""Create a fallback result when processing fails"""
|
| 283 |
+
try:
|
| 284 |
+
# Create basic parsed query
|
| 285 |
+
parsed_query = ParsedQuery(
|
| 286 |
+
original_query=query,
|
| 287 |
+
enhanced_query=query,
|
| 288 |
+
query_type='general_inquiry',
|
| 289 |
+
entities={},
|
| 290 |
+
intent='information_seeking',
|
| 291 |
+
confidence=0.0,
|
| 292 |
+
keywords=[],
|
| 293 |
+
synonyms=[],
|
| 294 |
+
context={},
|
| 295 |
+
timestamp=datetime.now()
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
# Create fallback reasoning result
|
| 299 |
+
reasoning_result = ReasoningResult(
|
| 300 |
+
decision='pending',
|
| 301 |
+
confidence_score=0.0,
|
| 302 |
+
justification=f'Processing failed: {error}',
|
| 303 |
+
relevant_clauses=[],
|
| 304 |
+
reasoning_steps=['Processing failed'],
|
| 305 |
+
source_references=[]
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
return QueryResult(
|
| 309 |
+
query=query,
|
| 310 |
+
parsed_query=parsed_query,
|
| 311 |
+
search_results=[],
|
| 312 |
+
reasoning_result=reasoning_result,
|
| 313 |
+
processing_time=0.0,
|
| 314 |
+
timestamp=datetime.now(),
|
| 315 |
+
audit_trail={
|
| 316 |
+
'action': 'query_processing',
|
| 317 |
+
'query': query,
|
| 318 |
+
'error': error,
|
| 319 |
+
'timestamp': datetime.now().isoformat(),
|
| 320 |
+
'status': 'error'
|
| 321 |
+
}
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
except Exception as e:
|
| 325 |
+
logger.error(f"Error creating fallback result: {e}")
|
| 326 |
+
raise
|
| 327 |
+
|
| 328 |
+
def _add_audit_entry(self, entry: Dict[str, Any]):
|
| 329 |
+
"""Add entry to audit log"""
|
| 330 |
+
try:
|
| 331 |
+
self.audit_log.append(entry)
|
| 332 |
+
|
| 333 |
+
# Keep audit log size manageable
|
| 334 |
+
if len(self.audit_log) > 1000:
|
| 335 |
+
self.audit_log = self.audit_log[-500:]
|
| 336 |
+
|
| 337 |
+
except Exception as e:
|
| 338 |
+
logger.error(f"Error adding audit entry: {e}")
|
| 339 |
+
|
| 340 |
+
def get_audit_trail(self) -> List[Dict[str, Any]]:
|
| 341 |
+
"""Get the complete audit trail"""
|
| 342 |
+
return self.audit_log.copy()
|
| 343 |
+
|
| 344 |
+
def save_audit_trail(self, file_path: str) -> bool:
|
| 345 |
+
"""Save audit trail to file"""
|
| 346 |
+
try:
|
| 347 |
+
with open(file_path, 'w') as f:
|
| 348 |
+
json.dump(self.audit_log, f, indent=2)
|
| 349 |
+
|
| 350 |
+
logger.info(f"Audit trail saved to: {file_path}")
|
| 351 |
+
return True
|
| 352 |
+
|
| 353 |
+
except Exception as e:
|
| 354 |
+
logger.error(f"Error saving audit trail: {e}")
|
| 355 |
+
return False
|
| 356 |
+
|
| 357 |
+
def get_system_statistics(self) -> Dict[str, Any]:
|
| 358 |
+
"""Get comprehensive system statistics"""
|
| 359 |
+
try:
|
| 360 |
+
# Get vector database statistics
|
| 361 |
+
db_stats = self.vector_database.get_document_statistics()
|
| 362 |
+
|
| 363 |
+
# Get audit trail statistics
|
| 364 |
+
audit_stats = {
|
| 365 |
+
'total_entries': len(self.audit_log),
|
| 366 |
+
'successful_queries': len([e for e in self.audit_log if e.get('status') == 'success']),
|
| 367 |
+
'failed_queries': len([e for e in self.audit_log if e.get('status') == 'error']),
|
| 368 |
+
'document_ingestions': len([e for e in self.audit_log if e.get('action') == 'document_ingestion']),
|
| 369 |
+
'query_processings': len([e for e in self.audit_log if e.get('action') == 'query_processing'])
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
# Get component information
|
| 373 |
+
component_info = {
|
| 374 |
+
'document_processor': 'AdvancedDocumentProcessor',
|
| 375 |
+
'vector_database': 'AdvancedVectorDatabase',
|
| 376 |
+
'query_parser': 'AdvancedQueryParser',
|
| 377 |
+
'reasoning_engine': 'AdvancedLLMReasoning',
|
| 378 |
+
'model_path': self.model_path,
|
| 379 |
+
'use_gpu': self.use_gpu
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
stats = {
|
| 383 |
+
'vector_database': db_stats,
|
| 384 |
+
'audit_trail': audit_stats,
|
| 385 |
+
'components': component_info,
|
| 386 |
+
'timestamp': datetime.now().isoformat()
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
return stats
|
| 390 |
+
|
| 391 |
+
except Exception as e:
|
| 392 |
+
logger.error(f"Error getting system statistics: {e}")
|
| 393 |
+
return {}
|
| 394 |
+
|
| 395 |
+
def clear_system(self) -> bool:
|
| 396 |
+
"""Clear all data from the system"""
|
| 397 |
+
try:
|
| 398 |
+
# Clear vector database
|
| 399 |
+
self.vector_database.clear_database()
|
| 400 |
+
|
| 401 |
+
# Clear audit log
|
| 402 |
+
self.audit_log = []
|
| 403 |
+
|
| 404 |
+
logger.info("System cleared successfully")
|
| 405 |
+
return True
|
| 406 |
+
|
| 407 |
+
except Exception as e:
|
| 408 |
+
logger.error(f"Error clearing system: {e}")
|
| 409 |
+
return False
|
| 410 |
+
|
| 411 |
+
def export_system_data(self, export_path: str) -> bool:
|
| 412 |
+
"""Export system data for backup or analysis"""
|
| 413 |
+
try:
|
| 414 |
+
# Get system statistics
|
| 415 |
+
stats = self.get_system_statistics()
|
| 416 |
+
|
| 417 |
+
# Add audit trail
|
| 418 |
+
export_data = {
|
| 419 |
+
'statistics': stats,
|
| 420 |
+
'audit_trail': self.audit_log,
|
| 421 |
+
'export_timestamp': datetime.now().isoformat()
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
with open(export_path, 'w') as f:
|
| 425 |
+
json.dump(export_data, f, indent=2)
|
| 426 |
+
|
| 427 |
+
logger.info(f"System data exported to: {export_path}")
|
| 428 |
+
return True
|
| 429 |
+
|
| 430 |
+
except Exception as e:
|
| 431 |
+
logger.error(f"Error exporting system data: {e}")
|
| 432 |
+
return False
|
| 433 |
+
|
| 434 |
+
def validate_system(self) -> Dict[str, Any]:
|
| 435 |
+
"""Validate system components and return status"""
|
| 436 |
+
try:
|
| 437 |
+
validation_results = {
|
| 438 |
+
'document_processor': True,
|
| 439 |
+
'vector_database': True,
|
| 440 |
+
'query_parser': True,
|
| 441 |
+
'reasoning_engine': True,
|
| 442 |
+
'overall_status': True,
|
| 443 |
+
'errors': []
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
# Test document processor
|
| 447 |
+
try:
|
| 448 |
+
# This is a basic test - in practice you might want more comprehensive tests
|
| 449 |
+
pass
|
| 450 |
+
except Exception as e:
|
| 451 |
+
validation_results['document_processor'] = False
|
| 452 |
+
validation_results['errors'].append(f"Document processor: {e}")
|
| 453 |
+
|
| 454 |
+
# Test vector database
|
| 455 |
+
try:
|
| 456 |
+
stats = self.vector_database.get_document_statistics()
|
| 457 |
+
except Exception as e:
|
| 458 |
+
validation_results['vector_database'] = False
|
| 459 |
+
validation_results['errors'].append(f"Vector database: {e}")
|
| 460 |
+
|
| 461 |
+
# Test query parser
|
| 462 |
+
try:
|
| 463 |
+
test_parsed = self.query_parser.parse_query("test query")
|
| 464 |
+
except Exception as e:
|
| 465 |
+
validation_results['query_parser'] = False
|
| 466 |
+
validation_results['errors'].append(f"Query parser: {e}")
|
| 467 |
+
|
| 468 |
+
# Test reasoning engine
|
| 469 |
+
try:
|
| 470 |
+
# Basic test - check if model file exists
|
| 471 |
+
if not os.path.exists(self.model_path):
|
| 472 |
+
validation_results['reasoning_engine'] = False
|
| 473 |
+
validation_results['errors'].append("LLM model file not found")
|
| 474 |
+
except Exception as e:
|
| 475 |
+
validation_results['reasoning_engine'] = False
|
| 476 |
+
validation_results['errors'].append(f"Reasoning engine: {e}")
|
| 477 |
+
|
| 478 |
+
# Overall status
|
| 479 |
+
validation_results['overall_status'] = all([
|
| 480 |
+
validation_results['document_processor'],
|
| 481 |
+
validation_results['vector_database'],
|
| 482 |
+
validation_results['query_parser'],
|
| 483 |
+
validation_results['reasoning_engine']
|
| 484 |
+
])
|
| 485 |
+
|
| 486 |
+
return validation_results
|
| 487 |
+
|
| 488 |
+
except Exception as e:
|
| 489 |
+
logger.error(f"Error validating system: {e}")
|
| 490 |
+
return {
|
| 491 |
+
'overall_status': False,
|
| 492 |
+
'errors': [f"Validation failed: {e}"]
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
# Example usage
|
| 496 |
+
if __name__ == "__main__":
|
| 497 |
+
# Initialize RAG system
|
| 498 |
+
rag_system = AdvancedRAGSystem(use_gpu=True)
|
| 499 |
+
|
| 500 |
+
# Test system validation
|
| 501 |
+
validation = rag_system.validate_system()
|
| 502 |
+
print(f"System validation: {validation['overall_status']}")
|
| 503 |
+
|
| 504 |
+
if validation['overall_status']:
|
| 505 |
+
print("✅ All components are working correctly")
|
| 506 |
+
else:
|
| 507 |
+
print("❌ Some components have issues:")
|
| 508 |
+
for error in validation['errors']:
|
| 509 |
+
print(f" - {error}")
|
| 510 |
+
|
| 511 |
+
# Get system statistics
|
| 512 |
+
stats = rag_system.get_system_statistics()
|
| 513 |
+
print(f"\nSystem statistics: {stats}")
|
rag_system_gpp.py
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GPU-Optimized RAG System for High Performance
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import logging
|
| 8 |
+
import hashlib
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 11 |
+
from dataclasses import dataclass, asdict
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import fitz # PyMuPDF
|
| 15 |
+
import pytesseract
|
| 16 |
+
from pdf2image import convert_from_path
|
| 17 |
+
from PIL import Image
|
| 18 |
+
import cv2
|
| 19 |
+
import numpy as np
|
| 20 |
+
from docx import Document
|
| 21 |
+
from bs4 import BeautifulSoup
|
| 22 |
+
import requests
|
| 23 |
+
|
| 24 |
+
from sentence_transformers import SentenceTransformer
|
| 25 |
+
import chromadb
|
| 26 |
+
from chromadb.config import Settings
|
| 27 |
+
|
| 28 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 29 |
+
from langchain.schema import Document
|
| 30 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 31 |
+
from langchain_chroma import Chroma
|
| 32 |
+
from langchain.retrievers import ContextualCompressionRetriever
|
| 33 |
+
from langchain.retrievers.document_compressors import LLMChainExtractor
|
| 34 |
+
|
| 35 |
+
# Disable ChromaDB telemetry
|
| 36 |
+
os.environ["ANONYMIZED_TELEMETRY"] = "False"
|
| 37 |
+
|
| 38 |
+
# Configure logging
|
| 39 |
+
logging.basicConfig(level=logging.INFO)
|
| 40 |
+
logger = logging.getLogger(__name__)
|
| 41 |
+
|
| 42 |
+
@dataclass
|
| 43 |
+
class DocumentChunk:
|
| 44 |
+
"""Represents a chunk of processed document with metadata"""
|
| 45 |
+
chunk_id: str
|
| 46 |
+
content: str
|
| 47 |
+
source_file: str
|
| 48 |
+
page_number: Optional[int] = None
|
| 49 |
+
chunk_index: Optional[int] = None
|
| 50 |
+
category: Optional[str] = None
|
| 51 |
+
embedding: Optional[List[float]] = None
|
| 52 |
+
|
| 53 |
+
@dataclass
|
| 54 |
+
class QueryResult:
|
| 55 |
+
"""Structured result from query processing"""
|
| 56 |
+
decision: str # approved/rejected/conditional
|
| 57 |
+
justification: str
|
| 58 |
+
relevant_clauses: List[str]
|
| 59 |
+
confidence_score: float
|
| 60 |
+
audit_trail: Dict[str, Any]
|
| 61 |
+
amount: Optional[float] = None
|
| 62 |
+
|
| 63 |
+
class DocumentProcessor:
|
| 64 |
+
"""Handles document ingestion and preprocessing with OCR support"""
|
| 65 |
+
|
| 66 |
+
def __init__(self, ocr_language='eng'):
|
| 67 |
+
self.ocr_language = ocr_language
|
| 68 |
+
self.text_splitter = RecursiveCharacterTextSplitter(
|
| 69 |
+
chunk_size=1000,
|
| 70 |
+
chunk_overlap=200,
|
| 71 |
+
separators=["\n\n", "\n", ". ", " ", ""]
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def extract_text_from_pdf(self, pdf_path: str, use_ocr: bool = False) -> str:
|
| 75 |
+
"""Extract text from PDF with optional OCR for scanned documents"""
|
| 76 |
+
try:
|
| 77 |
+
doc = fitz.open(pdf_path)
|
| 78 |
+
text = ""
|
| 79 |
+
|
| 80 |
+
for page_num in range(len(doc)):
|
| 81 |
+
page = doc.load_page(page_num)
|
| 82 |
+
|
| 83 |
+
# Try to extract text normally first
|
| 84 |
+
page_text = page.get_text()
|
| 85 |
+
|
| 86 |
+
# If no text found or very little text, use OCR
|
| 87 |
+
if use_ocr or len(page_text.strip()) < 50:
|
| 88 |
+
logger.info(f"Using OCR for page {page_num + 1}")
|
| 89 |
+
try:
|
| 90 |
+
pix = page.get_pixmap()
|
| 91 |
+
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
| 92 |
+
|
| 93 |
+
# Convert to grayscale for better OCR
|
| 94 |
+
img_gray = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2GRAY)
|
| 95 |
+
|
| 96 |
+
# Apply preprocessing for better OCR
|
| 97 |
+
img_processed = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
| 98 |
+
|
| 99 |
+
# Extract text using OCR
|
| 100 |
+
page_text = pytesseract.image_to_string(
|
| 101 |
+
img_processed,
|
| 102 |
+
lang=self.ocr_language,
|
| 103 |
+
config='--psm 6'
|
| 104 |
+
)
|
| 105 |
+
except Exception as ocr_error:
|
| 106 |
+
logger.warning(f"OCR failed for page {page_num + 1}: {ocr_error}")
|
| 107 |
+
logger.warning("Continuing with existing text extraction")
|
| 108 |
+
# Keep the existing page_text (from normal extraction)
|
| 109 |
+
|
| 110 |
+
text += f"\n\n--- Page {page_num + 1} ---\n{page_text}"
|
| 111 |
+
|
| 112 |
+
doc.close()
|
| 113 |
+
return text
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
logger.error(f"Error extracting text from PDF {pdf_path}: {e}")
|
| 117 |
+
raise
|
| 118 |
+
|
| 119 |
+
def chunk_document(self, text: str, source_file: str) -> List[DocumentChunk]:
|
| 120 |
+
"""Chunk document into semantically coherent passages"""
|
| 121 |
+
try:
|
| 122 |
+
# Use LangChain's text splitter for better semantic chunking
|
| 123 |
+
docs = [Document(page_content=text, metadata={"source": source_file})]
|
| 124 |
+
split_docs = self.text_splitter.split_documents(docs)
|
| 125 |
+
|
| 126 |
+
chunks = []
|
| 127 |
+
for i, doc in enumerate(split_docs):
|
| 128 |
+
chunk = DocumentChunk(
|
| 129 |
+
chunk_id=f"chunk_{i+1}_{hashlib.md5(doc.page_content.encode()).hexdigest()[:8]}",
|
| 130 |
+
content=doc.page_content.strip(),
|
| 131 |
+
source_file=source_file,
|
| 132 |
+
chunk_index=i
|
| 133 |
+
)
|
| 134 |
+
chunks.append(chunk)
|
| 135 |
+
|
| 136 |
+
return chunks
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"Error chunking document: {e}")
|
| 140 |
+
raise
|
| 141 |
+
|
| 142 |
+
class VectorDatabase:
|
| 143 |
+
"""Manages vector storage and retrieval with GPU optimization"""
|
| 144 |
+
|
| 145 |
+
def _clean_metadata(self, metadata_dict: Dict[str, Any]) -> Dict[str, Any]:
|
| 146 |
+
"""Clean metadata by removing None values and converting to proper types"""
|
| 147 |
+
clean_metadata = {}
|
| 148 |
+
for key, value in metadata_dict.items():
|
| 149 |
+
if value is not None:
|
| 150 |
+
if isinstance(value, (int, float)):
|
| 151 |
+
clean_metadata[key] = value
|
| 152 |
+
else:
|
| 153 |
+
clean_metadata[key] = str(value)
|
| 154 |
+
return clean_metadata
|
| 155 |
+
|
| 156 |
+
def __init__(self, persist_directory: str = "./vector_db", use_gpu: bool = True):
|
| 157 |
+
self.persist_directory = persist_directory
|
| 158 |
+
|
| 159 |
+
# GPU-optimized embeddings
|
| 160 |
+
device = 'cuda' if use_gpu else 'cpu'
|
| 161 |
+
self.embeddings = HuggingFaceEmbeddings(
|
| 162 |
+
model_name="all-MiniLM-L6-v2",
|
| 163 |
+
model_kwargs={'device': device}
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
# Initialize ChromaDB
|
| 167 |
+
import chromadb
|
| 168 |
+
from chromadb.config import Settings
|
| 169 |
+
|
| 170 |
+
# Create ChromaDB client with proper settings
|
| 171 |
+
client = chromadb.PersistentClient(
|
| 172 |
+
path=persist_directory,
|
| 173 |
+
settings=Settings(
|
| 174 |
+
anonymized_telemetry=False,
|
| 175 |
+
is_persistent=True
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# Create collection for documents
|
| 180 |
+
collection_name = "insurance_documents"
|
| 181 |
+
try:
|
| 182 |
+
self.collection = client.get_collection(collection_name)
|
| 183 |
+
except:
|
| 184 |
+
self.collection = client.create_collection(collection_name)
|
| 185 |
+
|
| 186 |
+
self.vectorstore = Chroma(
|
| 187 |
+
embedding_function=self.embeddings,
|
| 188 |
+
persist_directory=persist_directory,
|
| 189 |
+
client=client,
|
| 190 |
+
collection_name=collection_name
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
def add_documents(self, chunks: List[DocumentChunk]) -> None:
|
| 194 |
+
"""Add document chunks to vector database"""
|
| 195 |
+
try:
|
| 196 |
+
documents = []
|
| 197 |
+
metadatas = []
|
| 198 |
+
ids = []
|
| 199 |
+
|
| 200 |
+
for chunk in chunks:
|
| 201 |
+
documents.append(chunk.content)
|
| 202 |
+
|
| 203 |
+
# Create metadata dictionary
|
| 204 |
+
metadata = {
|
| 205 |
+
"chunk_id": chunk.chunk_id,
|
| 206 |
+
"source_file": chunk.source_file,
|
| 207 |
+
"page_number": chunk.page_number,
|
| 208 |
+
"chunk_index": chunk.chunk_index,
|
| 209 |
+
"category": chunk.category
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
# Clean metadata using helper function
|
| 213 |
+
clean_metadata = self._clean_metadata(metadata)
|
| 214 |
+
metadatas.append(clean_metadata)
|
| 215 |
+
ids.append(chunk.chunk_id)
|
| 216 |
+
|
| 217 |
+
# Get embeddings for documents (GPU-accelerated)
|
| 218 |
+
embeddings = self.embeddings.embed_documents(documents)
|
| 219 |
+
|
| 220 |
+
# Add to collection
|
| 221 |
+
self.collection.add(
|
| 222 |
+
documents=documents,
|
| 223 |
+
metadatas=metadatas,
|
| 224 |
+
embeddings=embeddings,
|
| 225 |
+
ids=ids
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
logger.info(f"Added {len(chunks)} chunks to vector database")
|
| 229 |
+
|
| 230 |
+
except Exception as e:
|
| 231 |
+
logger.error(f"Error adding documents to vector database: {e}")
|
| 232 |
+
raise
|
| 233 |
+
|
| 234 |
+
def search(self, query: str, k: int = 5) -> List[Dict[str, Any]]:
|
| 235 |
+
"""Search for relevant document chunks"""
|
| 236 |
+
try:
|
| 237 |
+
# Get query embedding (GPU-accelerated)
|
| 238 |
+
query_embedding = self.embeddings.embed_query(query)
|
| 239 |
+
|
| 240 |
+
# Search in collection
|
| 241 |
+
results = self.collection.query(
|
| 242 |
+
query_embeddings=[query_embedding],
|
| 243 |
+
n_results=k,
|
| 244 |
+
include=["documents", "metadatas", "distances"]
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
search_results = []
|
| 248 |
+
if results['documents'] and results['documents'][0]:
|
| 249 |
+
for i, (doc, metadata, distance) in enumerate(zip(
|
| 250 |
+
results['documents'][0],
|
| 251 |
+
results['metadatas'][0],
|
| 252 |
+
results['distances'][0]
|
| 253 |
+
)):
|
| 254 |
+
# Clean metadata using helper function
|
| 255 |
+
clean_metadata = self._clean_metadata(metadata)
|
| 256 |
+
|
| 257 |
+
search_results.append({
|
| 258 |
+
"content": doc,
|
| 259 |
+
"metadata": clean_metadata,
|
| 260 |
+
"similarity_score": 1.0 - float(distance) # Convert distance to similarity
|
| 261 |
+
})
|
| 262 |
+
|
| 263 |
+
return search_results
|
| 264 |
+
|
| 265 |
+
except Exception as e:
|
| 266 |
+
logger.error(f"Error searching vector database: {e}")
|
| 267 |
+
raise
|
| 268 |
+
|
| 269 |
+
class QueryParser:
|
| 270 |
+
"""Parses and structures natural language queries"""
|
| 271 |
+
|
| 272 |
+
def __init__(self, llm_model):
|
| 273 |
+
self.llm = llm_model
|
| 274 |
+
|
| 275 |
+
def extract_entities(self, query: str) -> Dict[str, Any]:
|
| 276 |
+
"""Extract structured entities from natural language query"""
|
| 277 |
+
try:
|
| 278 |
+
prompt = f"""
|
| 279 |
+
Extract structured information from the following query about insurance/policy:
|
| 280 |
+
|
| 281 |
+
Query: {query}
|
| 282 |
+
|
| 283 |
+
Extract the following information in JSON format:
|
| 284 |
+
{{
|
| 285 |
+
"age": <age if mentioned>,
|
| 286 |
+
"procedure": <medical procedure if mentioned>,
|
| 287 |
+
"location": <location if mentioned>,
|
| 288 |
+
"policy_type": <type of policy mentioned>,
|
| 289 |
+
"claim_amount": <amount if mentioned>,
|
| 290 |
+
"condition": <medical condition if mentioned>,
|
| 291 |
+
"intent": <what the user is asking about>
|
| 292 |
+
}}
|
| 293 |
+
|
| 294 |
+
JSON Response:
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
# Call LLM with proper format
|
| 298 |
+
if hasattr(self.llm, 'generate_content'): # Gemini
|
| 299 |
+
response = self.llm.generate_content(prompt).text
|
| 300 |
+
else: # Llama
|
| 301 |
+
response = self.llm(prompt, max_tokens=200, temperature=0.1, stop=["\n\n"])
|
| 302 |
+
if isinstance(response, dict):
|
| 303 |
+
response = response.get('choices', [{}])[0].get('text', '')
|
| 304 |
+
elif hasattr(response, 'choices'):
|
| 305 |
+
response = response.choices[0].text
|
| 306 |
+
|
| 307 |
+
# Parse response - handle both string and dict responses
|
| 308 |
+
try:
|
| 309 |
+
if isinstance(response, dict):
|
| 310 |
+
entities = response
|
| 311 |
+
else:
|
| 312 |
+
entities = json.loads(response)
|
| 313 |
+
return entities
|
| 314 |
+
except (json.JSONDecodeError, TypeError):
|
| 315 |
+
# Fallback parsing
|
| 316 |
+
return self._fallback_entity_extraction(query)
|
| 317 |
+
|
| 318 |
+
except Exception as e:
|
| 319 |
+
logger.error(f"Error extracting entities: {e}")
|
| 320 |
+
return self._fallback_entity_extraction(query)
|
| 321 |
+
|
| 322 |
+
def _fallback_entity_extraction(self, query: str) -> Dict[str, Any]:
|
| 323 |
+
"""Simple fallback entity extraction"""
|
| 324 |
+
entities = {
|
| 325 |
+
"age": None,
|
| 326 |
+
"procedure": None,
|
| 327 |
+
"location": None,
|
| 328 |
+
"policy_type": None,
|
| 329 |
+
"claim_amount": None,
|
| 330 |
+
"condition": None,
|
| 331 |
+
"intent": "general_inquiry"
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
# Simple keyword-based extraction
|
| 335 |
+
query_lower = query.lower()
|
| 336 |
+
|
| 337 |
+
# Extract age
|
| 338 |
+
import re
|
| 339 |
+
age_match = re.search(r'(\d+)\s*(?:years?|yrs?)', query_lower)
|
| 340 |
+
if age_match:
|
| 341 |
+
entities["age"] = int(age_match.group(1))
|
| 342 |
+
|
| 343 |
+
# Extract amount
|
| 344 |
+
amount_match = re.search(r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:rs?|rupees?|inr)', query_lower)
|
| 345 |
+
if amount_match:
|
| 346 |
+
entities["claim_amount"] = float(amount_match.group(1).replace(',', ''))
|
| 347 |
+
|
| 348 |
+
return entities
|
| 349 |
+
|
| 350 |
+
class LLMReasoning:
|
| 351 |
+
"""Handles LLM-based reasoning and decision logic"""
|
| 352 |
+
|
| 353 |
+
def __init__(self, llm_model):
|
| 354 |
+
self.llm = llm_model
|
| 355 |
+
|
| 356 |
+
def analyze_query(self, query: str, relevant_chunks: List[Dict], parsed_entities: Dict) -> QueryResult:
|
| 357 |
+
"""Analyze query against relevant document chunks"""
|
| 358 |
+
try:
|
| 359 |
+
# Prepare context from relevant chunks
|
| 360 |
+
context = "\n\n".join([
|
| 361 |
+
f"Document {i+1}:\n{chunk['content']}\nClause ID: {chunk['metadata'].get('chunk_id', 'N/A')}"
|
| 362 |
+
for i, chunk in enumerate(relevant_chunks)
|
| 363 |
+
])
|
| 364 |
+
|
| 365 |
+
# Create reasoning prompt
|
| 366 |
+
prompt = f"""
|
| 367 |
+
You are an insurance policy analyzer. Analyze the following query against the provided policy documents.
|
| 368 |
+
|
| 369 |
+
User Query: {query}
|
| 370 |
+
Extracted Entities: {json.dumps(parsed_entities, indent=2)}
|
| 371 |
+
|
| 372 |
+
Relevant Policy Clauses:
|
| 373 |
+
{context}
|
| 374 |
+
|
| 375 |
+
Please provide a structured analysis in the following JSON format:
|
| 376 |
+
{{
|
| 377 |
+
"decision": "approved/rejected/conditional",
|
| 378 |
+
"amount": <amount if applicable, null otherwise>,
|
| 379 |
+
"justification": "<detailed explanation with specific clause references>",
|
| 380 |
+
"relevant_clauses": ["<list of clause IDs that support the decision>"],
|
| 381 |
+
"confidence_score": <0.0 to 1.0>,
|
| 382 |
+
"conditions": ["<any conditions that must be met>"]
|
| 383 |
+
}}
|
| 384 |
+
|
| 385 |
+
Base your decision on:
|
| 386 |
+
1. Policy coverage and exclusions
|
| 387 |
+
2. Eligibility criteria
|
| 388 |
+
3. Waiting periods
|
| 389 |
+
4. Pre-existing conditions
|
| 390 |
+
5. Specific terms and conditions
|
| 391 |
+
|
| 392 |
+
JSON Response:
|
| 393 |
+
"""
|
| 394 |
+
|
| 395 |
+
# Call LLM with proper format
|
| 396 |
+
if hasattr(self.llm, 'generate_content'): # Gemini
|
| 397 |
+
response = self.llm.generate_content(prompt).text
|
| 398 |
+
else: # Llama
|
| 399 |
+
response = self.llm(prompt, max_tokens=500, temperature=0.1, stop=["\n\n"])
|
| 400 |
+
if isinstance(response, dict):
|
| 401 |
+
response = response.get('choices', [{}])[0].get('text', '')
|
| 402 |
+
elif hasattr(response, 'choices'):
|
| 403 |
+
response = response.choices[0].text
|
| 404 |
+
|
| 405 |
+
# Parse response - handle both string and dict responses
|
| 406 |
+
try:
|
| 407 |
+
if isinstance(response, dict):
|
| 408 |
+
result_data = response
|
| 409 |
+
else:
|
| 410 |
+
result_data = json.loads(response)
|
| 411 |
+
|
| 412 |
+
# Create audit trail
|
| 413 |
+
audit_trail = {
|
| 414 |
+
"timestamp": datetime.now().isoformat(),
|
| 415 |
+
"query": query,
|
| 416 |
+
"parsed_entities": parsed_entities,
|
| 417 |
+
"relevant_chunks_count": len(relevant_chunks),
|
| 418 |
+
"llm_prompt": prompt,
|
| 419 |
+
"llm_response": response,
|
| 420 |
+
"chunk_ids": [chunk['metadata'].get('chunk_id') for chunk in relevant_chunks]
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
return QueryResult(
|
| 424 |
+
decision=result_data.get("decision", "conditional"),
|
| 425 |
+
amount=result_data.get("amount"),
|
| 426 |
+
justification=result_data.get("justification", "Analysis incomplete"),
|
| 427 |
+
relevant_clauses=result_data.get("relevant_clauses", []),
|
| 428 |
+
confidence_score=result_data.get("confidence_score", 0.5),
|
| 429 |
+
audit_trail=audit_trail
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
except (json.JSONDecodeError, TypeError, AttributeError) as e:
|
| 433 |
+
# Fallback response
|
| 434 |
+
return QueryResult(
|
| 435 |
+
decision="conditional",
|
| 436 |
+
amount=None,
|
| 437 |
+
justification=f"Unable to parse LLM response: {str(e)}. Raw response: {str(response)[:200]}...",
|
| 438 |
+
relevant_clauses=[],
|
| 439 |
+
confidence_score=0.3,
|
| 440 |
+
audit_trail={
|
| 441 |
+
"timestamp": datetime.now().isoformat(),
|
| 442 |
+
"query": query,
|
| 443 |
+
"error": f"JSON parsing failed: {str(e)}",
|
| 444 |
+
"raw_response": str(response)[:500]
|
| 445 |
+
}
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
except Exception as e:
|
| 449 |
+
logger.error(f"Error in LLM reasoning: {e}")
|
| 450 |
+
return QueryResult(
|
| 451 |
+
decision="conditional",
|
| 452 |
+
amount=None,
|
| 453 |
+
justification=f"Error in analysis: {str(e)}",
|
| 454 |
+
relevant_clauses=[],
|
| 455 |
+
confidence_score=0.0,
|
| 456 |
+
audit_trail={
|
| 457 |
+
"timestamp": datetime.now().isoformat(),
|
| 458 |
+
"query": query,
|
| 459 |
+
"error": str(e)
|
| 460 |
+
}
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
class RAGSystem:
|
| 464 |
+
"""GPU-Optimized RAG system orchestrating all components"""
|
| 465 |
+
|
| 466 |
+
def __init__(self, model_path: str = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf", use_gpu: bool = True):
|
| 467 |
+
# Initialize components
|
| 468 |
+
self.document_processor = DocumentProcessor()
|
| 469 |
+
|
| 470 |
+
# Initialize vector database with GPU optimization
|
| 471 |
+
try:
|
| 472 |
+
self.vector_db = VectorDatabase(use_gpu=use_gpu)
|
| 473 |
+
logger.info(f"Vector database initialized successfully (GPU: {use_gpu})")
|
| 474 |
+
except Exception as e:
|
| 475 |
+
logger.error(f"Failed to initialize vector database: {e}")
|
| 476 |
+
raise
|
| 477 |
+
|
| 478 |
+
# Initialize LLM with GPU optimization
|
| 479 |
+
try:
|
| 480 |
+
logger.info(f"Loading model from: {model_path} (GPU: {use_gpu})")
|
| 481 |
+
|
| 482 |
+
from llama_cpp import Llama
|
| 483 |
+
|
| 484 |
+
# GPU-optimized configuration
|
| 485 |
+
if use_gpu:
|
| 486 |
+
self.llm = Llama(
|
| 487 |
+
model_path=model_path,
|
| 488 |
+
n_ctx=4096,
|
| 489 |
+
n_threads=8, # More threads for GPU
|
| 490 |
+
n_gpu_layers=35, # Use GPU layers
|
| 491 |
+
verbose=False,
|
| 492 |
+
use_mmap=True,
|
| 493 |
+
use_mlock=False,
|
| 494 |
+
seed=42
|
| 495 |
+
)
|
| 496 |
+
logger.info("GPU-optimized LLM model loaded successfully")
|
| 497 |
+
else:
|
| 498 |
+
# CPU fallback
|
| 499 |
+
self.llm = Llama(
|
| 500 |
+
model_path=model_path,
|
| 501 |
+
n_ctx=4096,
|
| 502 |
+
n_threads=4,
|
| 503 |
+
n_gpu_layers=0,
|
| 504 |
+
verbose=False,
|
| 505 |
+
use_mmap=True,
|
| 506 |
+
use_mlock=False,
|
| 507 |
+
seed=42
|
| 508 |
+
)
|
| 509 |
+
logger.info("CPU LLM model loaded successfully")
|
| 510 |
+
|
| 511 |
+
except Exception as e:
|
| 512 |
+
logger.warning(f"Could not load local model: {e}")
|
| 513 |
+
logger.info("Falling back to Gemini model")
|
| 514 |
+
try:
|
| 515 |
+
import google.generativeai as genai
|
| 516 |
+
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
|
| 517 |
+
self.llm = genai.GenerativeModel("gemini-1.5-pro")
|
| 518 |
+
logger.info("Gemini model loaded successfully")
|
| 519 |
+
except ImportError:
|
| 520 |
+
logger.error("Google Generative AI not available. Install with: pip install google-generativeai")
|
| 521 |
+
raise Exception("No LLM model available. Please install google-generativeai or ensure local model file exists.")
|
| 522 |
+
except Exception as gemini_error:
|
| 523 |
+
logger.error(f"Failed to load both local and Gemini models: {gemini_error}")
|
| 524 |
+
raise Exception("No LLM model available. Please check model file or API key.")
|
| 525 |
+
|
| 526 |
+
self.query_parser = QueryParser(self.llm)
|
| 527 |
+
self.reasoning_engine = LLMReasoning(self.llm)
|
| 528 |
+
|
| 529 |
+
# Audit trail storage
|
| 530 |
+
self.audit_log = []
|
| 531 |
+
|
| 532 |
+
def ingest_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
|
| 533 |
+
"""Ingest and process a document"""
|
| 534 |
+
try:
|
| 535 |
+
file_path = Path(file_path)
|
| 536 |
+
|
| 537 |
+
# Extract text based on file type
|
| 538 |
+
if file_path.suffix.lower() == '.pdf':
|
| 539 |
+
text = self.document_processor.extract_text_from_pdf(str(file_path), use_ocr)
|
| 540 |
+
elif file_path.suffix.lower() == '.docx':
|
| 541 |
+
text = self.document_processor.extract_text_from_docx(str(file_path))
|
| 542 |
+
elif file_path.suffix.lower() == '.html':
|
| 543 |
+
text = self.document_processor.extract_text_from_html(str(file_path))
|
| 544 |
+
elif file_path.suffix.lower() == '.eml':
|
| 545 |
+
text = self.document_processor.extract_text_from_email(str(file_path))
|
| 546 |
+
else:
|
| 547 |
+
raise ValueError(f"Unsupported file type: {file_path.suffix}")
|
| 548 |
+
|
| 549 |
+
# Chunk the document
|
| 550 |
+
chunks = self.document_processor.chunk_document(text, str(file_path))
|
| 551 |
+
|
| 552 |
+
# Add to vector database
|
| 553 |
+
self.vector_db.add_documents(chunks)
|
| 554 |
+
|
| 555 |
+
logger.info(f"Successfully ingested {len(chunks)} chunks from {file_path}")
|
| 556 |
+
return chunks
|
| 557 |
+
|
| 558 |
+
except Exception as e:
|
| 559 |
+
logger.error(f"Error ingesting document {file_path}: {e}")
|
| 560 |
+
raise
|
| 561 |
+
|
| 562 |
+
def process_query(self, query: str) -> QueryResult:
|
| 563 |
+
"""Process a natural language query"""
|
| 564 |
+
try:
|
| 565 |
+
# Step 1: Parse and structure the query
|
| 566 |
+
parsed_entities = self.query_parser.extract_entities(query)
|
| 567 |
+
|
| 568 |
+
# Step 2: Semantic retrieval
|
| 569 |
+
relevant_chunks = self.vector_db.search(query, k=5)
|
| 570 |
+
|
| 571 |
+
# Step 3: LLM reasoning and decision logic
|
| 572 |
+
result = self.reasoning_engine.analyze_query(query, relevant_chunks, parsed_entities)
|
| 573 |
+
|
| 574 |
+
# Step 4: Store audit trail
|
| 575 |
+
self.audit_log.append(result.audit_trail)
|
| 576 |
+
|
| 577 |
+
return result
|
| 578 |
+
|
| 579 |
+
except Exception as e:
|
| 580 |
+
logger.error(f"Error processing query: {e}")
|
| 581 |
+
return QueryResult(
|
| 582 |
+
decision="error",
|
| 583 |
+
amount=None,
|
| 584 |
+
justification=f"System error: {str(e)}",
|
| 585 |
+
relevant_clauses=[],
|
| 586 |
+
confidence_score=0.0,
|
| 587 |
+
audit_trail={
|
| 588 |
+
"timestamp": datetime.now().isoformat(),
|
| 589 |
+
"query": query,
|
| 590 |
+
"error": str(e)
|
| 591 |
+
}
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
def get_audit_trail(self) -> List[Dict]:
|
| 595 |
+
"""Get complete audit trail"""
|
| 596 |
+
return self.audit_log
|
| 597 |
+
|
| 598 |
+
def save_audit_trail(self, file_path: str):
|
| 599 |
+
"""Save audit trail to file"""
|
| 600 |
+
try:
|
| 601 |
+
with open(file_path, 'w') as f:
|
| 602 |
+
json.dump(self.audit_log, f, indent=2)
|
| 603 |
+
logger.info(f"Audit trail saved to {file_path}")
|
| 604 |
+
except Exception as e:
|
| 605 |
+
logger.error(f"Error saving audit trail: {e}")
|
| 606 |
+
|
| 607 |
+
# Example usage
|
| 608 |
+
if __name__ == "__main__":
|
| 609 |
+
# Initialize GPU-optimized RAG system
|
| 610 |
+
rag_system = RAGSystem(use_gpu=True) # Set to False for CPU-only
|
| 611 |
+
|
| 612 |
+
# Ingest sample document
|
| 613 |
+
print("Ingesting sample document...")
|
| 614 |
+
chunks = rag_system.ingest_document("sample.pdf", use_ocr=False)
|
| 615 |
+
|
| 616 |
+
# Process example queries
|
| 617 |
+
example_queries = [
|
| 618 |
+
"Is heart surgery covered under this policy?",
|
| 619 |
+
"What is the waiting period for pre-existing diseases?",
|
| 620 |
+
"Can I claim for dental treatment?",
|
| 621 |
+
"What is the maximum coverage amount?"
|
| 622 |
+
]
|
| 623 |
+
|
| 624 |
+
print("\nProcessing queries...")
|
| 625 |
+
for query in example_queries:
|
| 626 |
+
print(f"\nQuery: {query}")
|
| 627 |
+
result = rag_system.process_query(query)
|
| 628 |
+
print(f"Decision: {result.decision}")
|
| 629 |
+
print(f"Justification: {result.justification}")
|
| 630 |
+
print(f"Confidence: {result.confidence_score}")
|
| 631 |
+
|
| 632 |
+
# Save audit trail
|
| 633 |
+
rag_system.save_audit_trail("audit_trail.json")
|
startup.sh
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
uvicorn app:app --host 0.0.0.0 --port $PORT
|
test_api.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for the Flask API server
|
| 4 |
+
Demonstrates how to use the various endpoints
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
# API Configuration
|
| 12 |
+
BASE_URL = "http://127.0.0.1:5000"
|
| 13 |
+
HEADERS = {
|
| 14 |
+
"Content-Type": "application/json"
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
def test_health_check():
|
| 18 |
+
"""Test the health check endpoint"""
|
| 19 |
+
print("🔍 Testing health check...")
|
| 20 |
+
try:
|
| 21 |
+
response = requests.get(f"{BASE_URL}/health")
|
| 22 |
+
print(f"Status: {response.status_code}")
|
| 23 |
+
print(f"Response: {response.json()}")
|
| 24 |
+
return response.status_code == 200
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"❌ Health check failed: {e}")
|
| 27 |
+
return False
|
| 28 |
+
|
| 29 |
+
def test_system_status():
|
| 30 |
+
"""Test the system status endpoint"""
|
| 31 |
+
print("\n📊 Testing system status...")
|
| 32 |
+
try:
|
| 33 |
+
response = requests.get(f"{BASE_URL}/hackrx/status")
|
| 34 |
+
print(f"Status: {response.status_code}")
|
| 35 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 36 |
+
return response.status_code == 200
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"❌ System status failed: {e}")
|
| 39 |
+
return False
|
| 40 |
+
|
| 41 |
+
def test_query_processing(questions):
|
| 42 |
+
"""Test query processing"""
|
| 43 |
+
print(f"\n🤔 Testing query processing...")
|
| 44 |
+
|
| 45 |
+
payload = {
|
| 46 |
+
"questions": questions
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
response = requests.post(f"{BASE_URL}/hackrx/run",
|
| 51 |
+
json=payload,
|
| 52 |
+
headers=HEADERS)
|
| 53 |
+
|
| 54 |
+
print(f"Status: {response.status_code}")
|
| 55 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 56 |
+
return response.status_code == 200
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"❌ Query processing failed: {e}")
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
def test_upload_document(file_path):
|
| 62 |
+
"""Test document upload"""
|
| 63 |
+
print(f"\n📄 Testing document upload: {file_path}")
|
| 64 |
+
|
| 65 |
+
if not os.path.exists(file_path):
|
| 66 |
+
print(f"❌ File not found: {file_path}")
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
with open(file_path, 'rb') as f:
|
| 71 |
+
files = {'file': f}
|
| 72 |
+
|
| 73 |
+
response = requests.post(f"{BASE_URL}/hackrx/upload",
|
| 74 |
+
files=files)
|
| 75 |
+
|
| 76 |
+
print(f"Status: {response.status_code}")
|
| 77 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 78 |
+
return response.status_code == 200
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"❌ Upload failed: {e}")
|
| 81 |
+
return False
|
| 82 |
+
|
| 83 |
+
def test_query_processing(questions):
|
| 84 |
+
"""Test query processing"""
|
| 85 |
+
print(f"\n🤔 Testing query processing...")
|
| 86 |
+
|
| 87 |
+
payload = {
|
| 88 |
+
"questions": questions
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
response = requests.post(f"{BASE_URL}/hackrx/run",
|
| 93 |
+
json=payload,
|
| 94 |
+
headers=HEADERS)
|
| 95 |
+
|
| 96 |
+
print(f"Status: {response.status_code}")
|
| 97 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 98 |
+
return response.status_code == 200
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"❌ Query processing failed: {e}")
|
| 101 |
+
return False
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def main():
|
| 106 |
+
"""Main test function"""
|
| 107 |
+
print("🚀 Starting API Tests")
|
| 108 |
+
print("=" * 50)
|
| 109 |
+
|
| 110 |
+
# Test 1: Health check
|
| 111 |
+
if not test_health_check():
|
| 112 |
+
print("❌ Health check failed. Make sure the server is running.")
|
| 113 |
+
return
|
| 114 |
+
|
| 115 |
+
# Test 2: System status
|
| 116 |
+
test_system_status()
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Test 4: Document upload (if file exists)
|
| 121 |
+
test_files = ["doc2.pdf", "test_document.txt"]
|
| 122 |
+
for test_file in test_files:
|
| 123 |
+
if os.path.exists(test_file):
|
| 124 |
+
test_upload_document(test_file)
|
| 125 |
+
break
|
| 126 |
+
|
| 127 |
+
# Test 3: Query processing
|
| 128 |
+
test_questions = [
|
| 129 |
+
"What is covered under this policy?",
|
| 130 |
+
"What is the maximum coverage amount?",
|
| 131 |
+
"What documents are required for claims?"
|
| 132 |
+
]
|
| 133 |
+
test_query_processing(test_questions)
|
| 134 |
+
|
| 135 |
+
print("\n✅ API tests completed!")
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
main()
|
test_app2.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test app2.py API Server
|
| 4 |
+
This script tests the new app2.py Flask API server
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
# Your ngrok URL
|
| 12 |
+
BASE_URL = "https://0468c638cef7.ngrok-free.app"
|
| 13 |
+
|
| 14 |
+
def test_health():
|
| 15 |
+
"""Test health endpoint"""
|
| 16 |
+
print("🧪 Testing Health Endpoint...")
|
| 17 |
+
try:
|
| 18 |
+
response = requests.get(f"{BASE_URL}/api/health", timeout=10)
|
| 19 |
+
print(f"Status: {response.status_code}")
|
| 20 |
+
if response.status_code == 200:
|
| 21 |
+
print("✅ Health check passed!")
|
| 22 |
+
print(f"Response: {response.json()}")
|
| 23 |
+
else:
|
| 24 |
+
print(f"❌ Health check failed: {response.text}")
|
| 25 |
+
return response.status_code == 200
|
| 26 |
+
except Exception as e:
|
| 27 |
+
print(f"❌ Error: {e}")
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
def test_root():
|
| 31 |
+
"""Test root endpoint"""
|
| 32 |
+
print("\n🧪 Testing Root Endpoint...")
|
| 33 |
+
try:
|
| 34 |
+
response = requests.get(f"{BASE_URL}/", timeout=10)
|
| 35 |
+
print(f"Status: {response.status_code}")
|
| 36 |
+
if response.status_code == 200:
|
| 37 |
+
print("✅ Root endpoint passed!")
|
| 38 |
+
data = response.json()
|
| 39 |
+
print(f"Message: {data.get('message')}")
|
| 40 |
+
print(f"Version: {data.get('version')}")
|
| 41 |
+
print(f"Endpoints: {len(data.get('endpoints', {}))} available")
|
| 42 |
+
else:
|
| 43 |
+
print(f"❌ Root endpoint failed: {response.text}")
|
| 44 |
+
return response.status_code == 200
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"❌ Error: {e}")
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
def test_simple_query():
|
| 50 |
+
"""Test simple query without document URL"""
|
| 51 |
+
print("\n🧪 Testing Simple Query...")
|
| 52 |
+
url = f"{BASE_URL}/hackrx/run"
|
| 53 |
+
headers = {
|
| 54 |
+
"Content-Type": "application/json",
|
| 55 |
+
"Accept": "application/json",
|
| 56 |
+
"Authorization": "Bearer test_key_123"
|
| 57 |
+
}
|
| 58 |
+
payload = {
|
| 59 |
+
"questions": [
|
| 60 |
+
"What is the grace period for premium payment?",
|
| 61 |
+
"What is the waiting period for pre-existing diseases?"
|
| 62 |
+
]
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
response = requests.post(url, json=payload, headers=headers, timeout=60)
|
| 67 |
+
print(f"Status: {response.status_code}")
|
| 68 |
+
if response.status_code == 200:
|
| 69 |
+
print("✅ Simple query passed!")
|
| 70 |
+
result = response.json()
|
| 71 |
+
print(f"Answers: {len(result.get('answers', []))} received")
|
| 72 |
+
for i, answer in enumerate(result.get('answers', [])):
|
| 73 |
+
print(f" {i+1}. {answer[:100]}...")
|
| 74 |
+
else:
|
| 75 |
+
print(f"❌ Simple query failed: {response.text}")
|
| 76 |
+
return response.status_code == 200
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"❌ Error: {e}")
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
def test_hackathon_format():
|
| 82 |
+
"""Test hackathon format with document URL"""
|
| 83 |
+
print("\n🧪 Testing Hackathon Format...")
|
| 84 |
+
url = f"{BASE_URL}/hackrx/run"
|
| 85 |
+
headers = {
|
| 86 |
+
"Content-Type": "application/json",
|
| 87 |
+
"Accept": "application/json",
|
| 88 |
+
"Authorization": "Bearer test_key_123"
|
| 89 |
+
}
|
| 90 |
+
payload = {
|
| 91 |
+
"documents": "https://hackrx.blob.core.windows.net/assets/policy.pdf?sv=2023-01-03&st=2025-07-04T09%3A11%3A24Z&se=2027-07-05T09%3A11%3A00Z&sr=b&sp=r&sig=N4a9OU0w0QXO6AOIBiu4bpl7AXvEZogeT%2FjUHNO7HzQ%3D",
|
| 92 |
+
"questions": [
|
| 93 |
+
"What is the grace period for premium payment under the National Parivar Mediclaim Plus Policy?",
|
| 94 |
+
"What is the waiting period for pre-existing diseases (PED) to be covered?",
|
| 95 |
+
"Does this policy cover maternity expenses, and what are the conditions?",
|
| 96 |
+
"What is the waiting period for cataract surgery?",
|
| 97 |
+
"Are the medical expenses for an organ donor covered under this policy?"
|
| 98 |
+
]
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
print("⏳ Processing (this may take 30-60 seconds)...")
|
| 103 |
+
response = requests.post(url, json=payload, headers=headers, timeout=120)
|
| 104 |
+
print(f"Status: {response.status_code}")
|
| 105 |
+
if response.status_code == 200:
|
| 106 |
+
print("✅ Hackathon format passed!")
|
| 107 |
+
result = response.json()
|
| 108 |
+
print(f"Answers: {len(result.get('answers', []))} received")
|
| 109 |
+
for i, answer in enumerate(result.get('answers', [])):
|
| 110 |
+
print(f" {i+1}. {answer[:100]}...")
|
| 111 |
+
else:
|
| 112 |
+
print(f"❌ Hackathon format failed: {response.text}")
|
| 113 |
+
return response.status_code == 200
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print(f"❌ Error: {e}")
|
| 116 |
+
return False
|
| 117 |
+
|
| 118 |
+
def test_api_query():
|
| 119 |
+
"""Test API query endpoint"""
|
| 120 |
+
print("\n🧪 Testing API Query Endpoint...")
|
| 121 |
+
url = f"{BASE_URL}/api/query"
|
| 122 |
+
headers = {
|
| 123 |
+
"Content-Type": "application/json",
|
| 124 |
+
"Accept": "application/json"
|
| 125 |
+
}
|
| 126 |
+
payload = {
|
| 127 |
+
"query": "What is the grace period for premium payment?"
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
response = requests.post(url, json=payload, headers=headers, timeout=60)
|
| 132 |
+
print(f"Status: {response.status_code}")
|
| 133 |
+
if response.status_code == 200:
|
| 134 |
+
print("✅ API query passed!")
|
| 135 |
+
result = response.json()
|
| 136 |
+
print(f"Answer: {result.get('answer', '')[:100]}...")
|
| 137 |
+
print(f"Confidence: {result.get('confidence', 0)}")
|
| 138 |
+
print(f"Processing time: {result.get('processing_time', 0):.2f}s")
|
| 139 |
+
else:
|
| 140 |
+
print(f"❌ API query failed: {response.text}")
|
| 141 |
+
return response.status_code == 200
|
| 142 |
+
except Exception as e:
|
| 143 |
+
print(f"❌ Error: {e}")
|
| 144 |
+
return False
|
| 145 |
+
|
| 146 |
+
def main():
|
| 147 |
+
"""Run all tests"""
|
| 148 |
+
print("🚀 Testing app2.py API Server")
|
| 149 |
+
print("=" * 50)
|
| 150 |
+
print(f"Testing API at: {BASE_URL}")
|
| 151 |
+
print("=" * 50)
|
| 152 |
+
|
| 153 |
+
# Test health first
|
| 154 |
+
if not test_health():
|
| 155 |
+
print("\n❌ Health check failed! Make sure app2.py is running.")
|
| 156 |
+
print("Run: python app2.py")
|
| 157 |
+
return
|
| 158 |
+
|
| 159 |
+
# Test root endpoint
|
| 160 |
+
test_root()
|
| 161 |
+
|
| 162 |
+
# Test simple query
|
| 163 |
+
test_simple_query()
|
| 164 |
+
|
| 165 |
+
# Test hackathon format
|
| 166 |
+
test_hackathon_format()
|
| 167 |
+
|
| 168 |
+
# Test API query
|
| 169 |
+
test_api_query()
|
| 170 |
+
|
| 171 |
+
print("\n" + "=" * 50)
|
| 172 |
+
print("📋 APP2.PY TESTING GUIDE")
|
| 173 |
+
print("=" * 50)
|
| 174 |
+
print("1. Start the server:")
|
| 175 |
+
print(" python app2.py")
|
| 176 |
+
print()
|
| 177 |
+
print("2. Health Check:")
|
| 178 |
+
print(f" GET {BASE_URL}/api/health")
|
| 179 |
+
print()
|
| 180 |
+
print("3. Root Endpoint:")
|
| 181 |
+
print(f" GET {BASE_URL}/")
|
| 182 |
+
print()
|
| 183 |
+
print("4. Simple Query:")
|
| 184 |
+
print(f" POST {BASE_URL}/hackrx/run")
|
| 185 |
+
print(" Headers: Content-Type: application/json")
|
| 186 |
+
print(" Headers: Authorization: Bearer test_key_123")
|
| 187 |
+
print(" Body: {\"questions\": [\"Your question here\"]}")
|
| 188 |
+
print()
|
| 189 |
+
print("5. Hackathon Format:")
|
| 190 |
+
print(f" POST {BASE_URL}/hackrx/run")
|
| 191 |
+
print(" Headers: Content-Type: application/json")
|
| 192 |
+
print(" Headers: Authorization: Bearer test_key_123")
|
| 193 |
+
print(" Body: {\"documents\": \"URL\", \"questions\": [\"Q1\", \"Q2\"]}")
|
| 194 |
+
print()
|
| 195 |
+
print("6. API Query:")
|
| 196 |
+
print(f" POST {BASE_URL}/api/query")
|
| 197 |
+
print(" Headers: Content-Type: application/json")
|
| 198 |
+
print(" Body: {\"query\": \"Your question here\"}")
|
| 199 |
+
print()
|
| 200 |
+
print("7. Upload Document:")
|
| 201 |
+
print(f" POST {BASE_URL}/hackrx/upload")
|
| 202 |
+
print(" Body: form-data with 'file' field")
|
| 203 |
+
print("=" * 50)
|
| 204 |
+
|
| 205 |
+
if __name__ == "__main__":
|
| 206 |
+
main()
|
test_document_processing.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to isolate document processing issues
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def test_document_processor():
|
| 12 |
+
"""Test the document processor directly"""
|
| 13 |
+
print("🔍 TESTING DOCUMENT PROCESSOR")
|
| 14 |
+
print("=" * 40)
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
from document_processer import AdvancedDocumentProcessor
|
| 18 |
+
|
| 19 |
+
# Initialize processor
|
| 20 |
+
processor = AdvancedDocumentProcessor()
|
| 21 |
+
print("✅ Document processor initialized")
|
| 22 |
+
|
| 23 |
+
# Test with doc2.pdf
|
| 24 |
+
file_path = "doc2.pdf"
|
| 25 |
+
if not os.path.exists(file_path):
|
| 26 |
+
print(f"❌ File not found: {file_path}")
|
| 27 |
+
return
|
| 28 |
+
|
| 29 |
+
print(f"📄 Processing file: {file_path}")
|
| 30 |
+
|
| 31 |
+
# Test without OCR
|
| 32 |
+
print("\n--- Testing without OCR ---")
|
| 33 |
+
start_time = time.time()
|
| 34 |
+
try:
|
| 35 |
+
chunks = processor.process_document(file_path, use_ocr=False)
|
| 36 |
+
processing_time = time.time() - start_time
|
| 37 |
+
print(f"✅ Success! Processed {len(chunks)} chunks in {processing_time:.2f}s")
|
| 38 |
+
|
| 39 |
+
# Show first chunk
|
| 40 |
+
if chunks:
|
| 41 |
+
print(f"📋 First chunk preview:")
|
| 42 |
+
print(f" ID: {chunks[0].chunk_id}")
|
| 43 |
+
print(f" Content: {chunks[0].content[:100]}...")
|
| 44 |
+
print(f" Source: {chunks[0].source_file}")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"❌ Failed without OCR: {e}")
|
| 47 |
+
|
| 48 |
+
# Test with OCR
|
| 49 |
+
print("\n--- Testing with OCR ---")
|
| 50 |
+
start_time = time.time()
|
| 51 |
+
try:
|
| 52 |
+
chunks = processor.process_document(file_path, use_ocr=True)
|
| 53 |
+
processing_time = time.time() - start_time
|
| 54 |
+
print(f"✅ Success! Processed {len(chunks)} chunks in {processing_time:.2f}s")
|
| 55 |
+
|
| 56 |
+
# Show first chunk
|
| 57 |
+
if chunks:
|
| 58 |
+
print(f"📋 First chunk preview:")
|
| 59 |
+
print(f" ID: {chunks[0].chunk_id}")
|
| 60 |
+
print(f" Content: {chunks[0].content[:100]}...")
|
| 61 |
+
print(f" Source: {chunks[0].source_file}")
|
| 62 |
+
except Exception as e:
|
| 63 |
+
print(f"❌ Failed with OCR: {e}")
|
| 64 |
+
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"❌ Error initializing document processor: {e}")
|
| 67 |
+
|
| 68 |
+
def test_vector_database():
|
| 69 |
+
"""Test the vector database directly"""
|
| 70 |
+
print("\n🔍 TESTING VECTOR DATABASE")
|
| 71 |
+
print("=" * 40)
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
from vector_database import VectorDatabase
|
| 75 |
+
|
| 76 |
+
# Initialize vector database
|
| 77 |
+
vector_db = VectorDatabase()
|
| 78 |
+
print("✅ Vector database initialized")
|
| 79 |
+
|
| 80 |
+
# Test adding a simple document
|
| 81 |
+
test_content = "This is a test document for vector database testing."
|
| 82 |
+
test_metadata = {
|
| 83 |
+
'source_file': 'test.txt',
|
| 84 |
+
'file_type': 'text',
|
| 85 |
+
'section_type': 'test'
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
print("📝 Adding test document...")
|
| 89 |
+
success = vector_db.add_document(test_content, test_metadata)
|
| 90 |
+
|
| 91 |
+
if success:
|
| 92 |
+
print("✅ Successfully added test document")
|
| 93 |
+
|
| 94 |
+
# Test search
|
| 95 |
+
print("🔍 Testing search...")
|
| 96 |
+
results = vector_db.search_documents("test document", n_results=3)
|
| 97 |
+
print(f"✅ Search returned {len(results)} results")
|
| 98 |
+
else:
|
| 99 |
+
print("❌ Failed to add test document")
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"❌ Error with vector database: {e}")
|
| 103 |
+
|
| 104 |
+
def test_rag_system():
|
| 105 |
+
"""Test the RAG system directly"""
|
| 106 |
+
print("\n🔍 TESTING RAG SYSTEM")
|
| 107 |
+
print("=" * 40)
|
| 108 |
+
|
| 109 |
+
try:
|
| 110 |
+
from rag_system import AdvancedRAGSystem
|
| 111 |
+
|
| 112 |
+
# Initialize RAG system
|
| 113 |
+
print("🔄 Initializing RAG system...")
|
| 114 |
+
rag_system = AdvancedRAGSystem(use_gpu=False) # Use CPU for testing
|
| 115 |
+
print("✅ RAG system initialized")
|
| 116 |
+
|
| 117 |
+
# Test document ingestion
|
| 118 |
+
file_path = "doc2.pdf"
|
| 119 |
+
if os.path.exists(file_path):
|
| 120 |
+
print(f"📄 Testing document ingestion: {file_path}")
|
| 121 |
+
try:
|
| 122 |
+
chunks = rag_system.ingest_document(file_path, use_ocr=False)
|
| 123 |
+
print(f"✅ Successfully ingested {len(chunks)} chunks")
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f"❌ Document ingestion failed: {e}")
|
| 126 |
+
else:
|
| 127 |
+
print(f"❌ File not found: {file_path}")
|
| 128 |
+
|
| 129 |
+
except Exception as e:
|
| 130 |
+
print(f"❌ Error with RAG system: {e}")
|
| 131 |
+
|
| 132 |
+
def main():
|
| 133 |
+
"""Run all tests"""
|
| 134 |
+
print("🧪 DOCUMENT PROCESSING DIAGNOSTICS")
|
| 135 |
+
print("=" * 50)
|
| 136 |
+
|
| 137 |
+
# Test 1: Document processor
|
| 138 |
+
test_document_processor()
|
| 139 |
+
|
| 140 |
+
# Test 2: Vector database
|
| 141 |
+
test_vector_database()
|
| 142 |
+
|
| 143 |
+
# Test 3: RAG system
|
| 144 |
+
test_rag_system()
|
| 145 |
+
|
| 146 |
+
print("\n✅ Diagnostics completed!")
|
| 147 |
+
|
| 148 |
+
if __name__ == "__main__":
|
| 149 |
+
main()
|
test_document_to_vector.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Document to Vector Database Integration Test
|
| 3 |
+
Demonstrates the complete workflow: document_processer.py -> vector_database.py
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import tkinter as tk
|
| 9 |
+
from tkinter import filedialog
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import tempfile
|
| 12 |
+
import json
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
|
| 15 |
+
def select_file():
|
| 16 |
+
"""Open file dialog to select any supported document file"""
|
| 17 |
+
root = tk.Tk()
|
| 18 |
+
root.withdraw()
|
| 19 |
+
|
| 20 |
+
file_path = filedialog.askopenfilename(
|
| 21 |
+
title="Select a document to process and store in vector database",
|
| 22 |
+
filetypes=[
|
| 23 |
+
("All supported files", "*.pdf;*.txt;*.docx;*.html;*.htm;*.eml;*.msg;*.csv;*.json"),
|
| 24 |
+
("PDF files", "*.pdf"),
|
| 25 |
+
("Text files", "*.txt"),
|
| 26 |
+
("Word documents", "*.docx"),
|
| 27 |
+
("HTML files", "*.html;*.htm"),
|
| 28 |
+
("Email files", "*.eml;*.msg"),
|
| 29 |
+
("CSV files", "*.csv"),
|
| 30 |
+
("JSON files", "*.json"),
|
| 31 |
+
("All files", "*.*")
|
| 32 |
+
]
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
root.destroy()
|
| 36 |
+
return file_path
|
| 37 |
+
|
| 38 |
+
def process_and_store_document(file_path, use_ocr=False):
|
| 39 |
+
"""Process a document and store it in the vector database"""
|
| 40 |
+
try:
|
| 41 |
+
print(f"🔄 Step 1: Processing document with document_processer.py")
|
| 42 |
+
print(f"📄 File: {file_path}")
|
| 43 |
+
print(f"📏 File size: {os.path.getsize(file_path) / 1024:.1f} KB")
|
| 44 |
+
|
| 45 |
+
# Import and use document processor
|
| 46 |
+
from document_processer import AdvancedDocumentProcessor
|
| 47 |
+
|
| 48 |
+
# Initialize document processor
|
| 49 |
+
doc_processor = AdvancedDocumentProcessor()
|
| 50 |
+
|
| 51 |
+
# Process the document
|
| 52 |
+
chunks = doc_processor.process_document(file_path, use_ocr=use_ocr)
|
| 53 |
+
|
| 54 |
+
if not chunks:
|
| 55 |
+
print("❌ No chunks extracted from document")
|
| 56 |
+
return False, "No chunks extracted"
|
| 57 |
+
|
| 58 |
+
print(f"✅ Successfully processed {len(chunks)} chunks")
|
| 59 |
+
|
| 60 |
+
# Display chunk information
|
| 61 |
+
print(f"\n📋 Chunk Analysis:")
|
| 62 |
+
text_chunks = [c for c in chunks if c.section_type == 'main_text']
|
| 63 |
+
table_chunks = [c for c in chunks if c.section_type == 'table']
|
| 64 |
+
metadata_chunks = [c for c in chunks if c.section_type == 'metadata']
|
| 65 |
+
|
| 66 |
+
print(f" 📝 Text chunks: {len(text_chunks)}")
|
| 67 |
+
print(f" 📊 Table chunks: {len(table_chunks)}")
|
| 68 |
+
print(f" 🏷️ Metadata chunks: {len(metadata_chunks)}")
|
| 69 |
+
|
| 70 |
+
# Show sample chunks
|
| 71 |
+
for i, chunk in enumerate(chunks[:3]):
|
| 72 |
+
print(f"\n Chunk {i+1}:")
|
| 73 |
+
print(f" ID: {chunk.chunk_id}")
|
| 74 |
+
print(f" Type: {chunk.section_type}")
|
| 75 |
+
print(f" Content: {chunk.content[:100]}...")
|
| 76 |
+
|
| 77 |
+
print(f"\n🔄 Step 2: Storing in vector database")
|
| 78 |
+
|
| 79 |
+
# Import and use vector database
|
| 80 |
+
from vector_database import VectorDatabase
|
| 81 |
+
|
| 82 |
+
# Initialize vector database
|
| 83 |
+
vector_db = VectorDatabase(
|
| 84 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 85 |
+
collection_name="processed_documents",
|
| 86 |
+
persist_directory="./vector_db",
|
| 87 |
+
use_gpu=True
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
# Add documents to vector database
|
| 91 |
+
success = vector_db.add_documents(chunks)
|
| 92 |
+
|
| 93 |
+
if not success:
|
| 94 |
+
print("❌ Failed to store documents in vector database")
|
| 95 |
+
return False, "Vector database storage failed"
|
| 96 |
+
|
| 97 |
+
print(f"✅ Successfully stored {len(chunks)} chunks in vector database")
|
| 98 |
+
|
| 99 |
+
# Get database statistics
|
| 100 |
+
stats = vector_db.get_document_statistics()
|
| 101 |
+
print(f"\n📊 Vector Database Statistics:")
|
| 102 |
+
print(f" Total chunks: {stats.get('total_chunks', 0)}")
|
| 103 |
+
print(f" Unique sources: {stats.get('unique_sources', 0)}")
|
| 104 |
+
print(f" File types: {stats.get('file_types', [])}")
|
| 105 |
+
|
| 106 |
+
return True, chunks
|
| 107 |
+
|
| 108 |
+
except Exception as e:
|
| 109 |
+
print(f"❌ Error in process_and_store_document: {e}")
|
| 110 |
+
import traceback
|
| 111 |
+
traceback.print_exc()
|
| 112 |
+
return False, str(e)
|
| 113 |
+
|
| 114 |
+
def test_search_functionality(vector_db, original_file):
|
| 115 |
+
"""Test search functionality with the stored document"""
|
| 116 |
+
print(f"\n🔍 Step 3: Testing search functionality")
|
| 117 |
+
|
| 118 |
+
# Get filename for search terms
|
| 119 |
+
filename = Path(original_file).stem
|
| 120 |
+
|
| 121 |
+
# Create some test queries
|
| 122 |
+
test_queries = [
|
| 123 |
+
filename, # Search by filename
|
| 124 |
+
"document", # Generic search
|
| 125 |
+
"text content", # Content search
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
for query in test_queries:
|
| 129 |
+
print(f"\n🔍 Searching for: '{query}'")
|
| 130 |
+
|
| 131 |
+
# Semantic search
|
| 132 |
+
semantic_results = vector_db.search_similar(query, n_results=3)
|
| 133 |
+
print(f" 📝 Semantic search results: {len(semantic_results)}")
|
| 134 |
+
|
| 135 |
+
for i, result in enumerate(semantic_results[:2]):
|
| 136 |
+
print(f" Result {i+1}: Score {result.similarity_score:.3f}")
|
| 137 |
+
print(f" Source: {result.source_file}")
|
| 138 |
+
print(f" Content: {result.content[:80]}...")
|
| 139 |
+
|
| 140 |
+
# Hybrid search
|
| 141 |
+
hybrid_results = vector_db.hybrid_search(query, n_results=3)
|
| 142 |
+
print(f" 🔄 Hybrid search results: {len(hybrid_results)}")
|
| 143 |
+
|
| 144 |
+
for i, result in enumerate(hybrid_results[:2]):
|
| 145 |
+
print(f" Result {i+1}: Score {result.similarity_score:.3f}")
|
| 146 |
+
print(f" Source: {result.source_file}")
|
| 147 |
+
print(f" Content: {result.content[:80]}...")
|
| 148 |
+
|
| 149 |
+
def create_sample_documents():
|
| 150 |
+
"""Create sample documents for testing"""
|
| 151 |
+
test_dir = tempfile.mkdtemp()
|
| 152 |
+
print(f"📁 Created test directory: {test_dir}")
|
| 153 |
+
|
| 154 |
+
# Create sample TXT file
|
| 155 |
+
txt_content = """
|
| 156 |
+
Sample Document for Testing
|
| 157 |
+
|
| 158 |
+
This is a sample text document that will be processed and stored in the vector database.
|
| 159 |
+
It contains multiple paragraphs with various topics including:
|
| 160 |
+
|
| 161 |
+
1. Technology and AI
|
| 162 |
+
2. Business processes
|
| 163 |
+
3. Data analysis
|
| 164 |
+
4. Machine learning applications
|
| 165 |
+
|
| 166 |
+
The document processor should extract this content and create chunks.
|
| 167 |
+
The vector database should then store these chunks with embeddings.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
txt_path = os.path.join(test_dir, "sample_document.txt")
|
| 171 |
+
with open(txt_path, 'w', encoding='utf-8') as f:
|
| 172 |
+
f.write(txt_content)
|
| 173 |
+
|
| 174 |
+
# Create sample JSON file
|
| 175 |
+
json_data = {
|
| 176 |
+
"title": "Sample JSON Document",
|
| 177 |
+
"author": "Test User",
|
| 178 |
+
"content": "This is a sample JSON document for testing the document processor and vector database integration.",
|
| 179 |
+
"topics": ["document processing", "vector database", "AI", "machine learning"],
|
| 180 |
+
"metadata": {
|
| 181 |
+
"created": datetime.now().isoformat(),
|
| 182 |
+
"version": "1.0",
|
| 183 |
+
"tags": ["test", "sample", "integration"]
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
json_path = os.path.join(test_dir, "sample_data.json")
|
| 188 |
+
with open(json_path, 'w', encoding='utf-8') as f:
|
| 189 |
+
json.dump(json_data, f, indent=2)
|
| 190 |
+
|
| 191 |
+
return test_dir, {
|
| 192 |
+
'txt': txt_path,
|
| 193 |
+
'json': json_path
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
def main():
|
| 197 |
+
"""Main function to test document to vector database workflow"""
|
| 198 |
+
print("🚀 Document to Vector Database Integration Test")
|
| 199 |
+
print("="*60)
|
| 200 |
+
print("This test demonstrates the complete workflow:")
|
| 201 |
+
print("1. Process document with document_processer.py")
|
| 202 |
+
print("2. Store processed chunks in vector_database.py")
|
| 203 |
+
print("3. Test search functionality")
|
| 204 |
+
print()
|
| 205 |
+
|
| 206 |
+
# Check if required modules are available
|
| 207 |
+
try:
|
| 208 |
+
from document_processer import AdvancedDocumentProcessor
|
| 209 |
+
print("✅ Document processor available")
|
| 210 |
+
except ImportError as e:
|
| 211 |
+
print(f"❌ Document processor not available: {e}")
|
| 212 |
+
return
|
| 213 |
+
|
| 214 |
+
try:
|
| 215 |
+
from vector_database import VectorDatabase
|
| 216 |
+
print("✅ Vector database available")
|
| 217 |
+
except ImportError as e:
|
| 218 |
+
print(f"❌ Vector database not available: {e}")
|
| 219 |
+
return
|
| 220 |
+
|
| 221 |
+
print("\nChoose an option:")
|
| 222 |
+
print("1. Select a file to process")
|
| 223 |
+
print("2. Use sample documents")
|
| 224 |
+
print("3. Process from command line")
|
| 225 |
+
|
| 226 |
+
choice = input("Enter choice (1, 2, or 3): ").strip()
|
| 227 |
+
|
| 228 |
+
if choice == "1":
|
| 229 |
+
# Select file
|
| 230 |
+
print("\n📁 Please select a document to process...")
|
| 231 |
+
file_path = select_file()
|
| 232 |
+
|
| 233 |
+
if not file_path:
|
| 234 |
+
print("❌ No file selected")
|
| 235 |
+
return
|
| 236 |
+
|
| 237 |
+
# Ask about OCR
|
| 238 |
+
use_ocr = input("Use OCR for PDFs? (y/n): ").lower().strip() in ['y', 'yes']
|
| 239 |
+
|
| 240 |
+
# Process and store
|
| 241 |
+
success, result = process_and_store_document(file_path, use_ocr)
|
| 242 |
+
|
| 243 |
+
if success:
|
| 244 |
+
# Test search functionality
|
| 245 |
+
vector_db = VectorDatabase()
|
| 246 |
+
test_search_functionality(vector_db, file_path)
|
| 247 |
+
|
| 248 |
+
print(f"\n🎉 Complete workflow successful!")
|
| 249 |
+
print(f"📄 Processed: {file_path}")
|
| 250 |
+
print(f"📊 Stored in vector database")
|
| 251 |
+
print(f"🔍 Search functionality tested")
|
| 252 |
+
|
| 253 |
+
elif choice == "2":
|
| 254 |
+
# Use sample documents
|
| 255 |
+
test_dir, sample_files = create_sample_documents()
|
| 256 |
+
|
| 257 |
+
print(f"\n🧪 Testing with sample documents...")
|
| 258 |
+
|
| 259 |
+
for file_type, file_path in sample_files.items():
|
| 260 |
+
print(f"\n📄 Processing {file_type.upper()} file...")
|
| 261 |
+
success, result = process_and_store_document(file_path)
|
| 262 |
+
|
| 263 |
+
if success:
|
| 264 |
+
print(f"✅ {file_type.upper()} file processed successfully")
|
| 265 |
+
else:
|
| 266 |
+
print(f"❌ {file_type.upper()} file failed: {result}")
|
| 267 |
+
|
| 268 |
+
# Clean up
|
| 269 |
+
import shutil
|
| 270 |
+
shutil.rmtree(test_dir, ignore_errors=True)
|
| 271 |
+
print(f"\n🧹 Cleaned up test directory")
|
| 272 |
+
|
| 273 |
+
elif choice == "3":
|
| 274 |
+
# Command line processing
|
| 275 |
+
if len(sys.argv) < 2:
|
| 276 |
+
print("❌ Usage: python test_document_to_vector.py <file_path> [--ocr]")
|
| 277 |
+
return
|
| 278 |
+
|
| 279 |
+
file_path = sys.argv[1]
|
| 280 |
+
use_ocr = "--ocr" in sys.argv
|
| 281 |
+
|
| 282 |
+
if not os.path.exists(file_path):
|
| 283 |
+
print(f"❌ File not found: {file_path}")
|
| 284 |
+
return
|
| 285 |
+
|
| 286 |
+
print(f"🔄 Processing file from command line: {file_path}")
|
| 287 |
+
success, result = process_and_store_document(file_path, use_ocr)
|
| 288 |
+
|
| 289 |
+
if success:
|
| 290 |
+
print(f"🎉 Successfully processed and stored: {file_path}")
|
| 291 |
+
else:
|
| 292 |
+
print(f"❌ Failed: {result}")
|
| 293 |
+
|
| 294 |
+
else:
|
| 295 |
+
print("❌ Invalid choice")
|
| 296 |
+
|
| 297 |
+
def batch_process_directory():
|
| 298 |
+
"""Process all supported files in a directory"""
|
| 299 |
+
print("🔄 Batch Processing Directory")
|
| 300 |
+
print("="*40)
|
| 301 |
+
|
| 302 |
+
# Select directory
|
| 303 |
+
root = tk.Tk()
|
| 304 |
+
root.withdraw()
|
| 305 |
+
directory = filedialog.askdirectory(title="Select directory containing documents")
|
| 306 |
+
root.destroy()
|
| 307 |
+
|
| 308 |
+
if not directory:
|
| 309 |
+
print("❌ No directory selected")
|
| 310 |
+
return
|
| 311 |
+
|
| 312 |
+
# Find supported files
|
| 313 |
+
supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
|
| 314 |
+
files_to_process = []
|
| 315 |
+
|
| 316 |
+
for ext in supported_extensions:
|
| 317 |
+
files_to_process.extend(Path(directory).glob(f"*{ext}"))
|
| 318 |
+
|
| 319 |
+
if not files_to_process:
|
| 320 |
+
print("❌ No supported files found in directory")
|
| 321 |
+
return
|
| 322 |
+
|
| 323 |
+
print(f"📁 Found {len(files_to_process)} files to process")
|
| 324 |
+
|
| 325 |
+
# Initialize vector database
|
| 326 |
+
vector_db = VectorDatabase()
|
| 327 |
+
|
| 328 |
+
# Process each file
|
| 329 |
+
results = {}
|
| 330 |
+
for file_path in files_to_process:
|
| 331 |
+
print(f"\n🔄 Processing: {file_path.name}")
|
| 332 |
+
|
| 333 |
+
success, result = process_and_store_document(str(file_path))
|
| 334 |
+
|
| 335 |
+
if success:
|
| 336 |
+
print(f"✅ Success: {len(result)} chunks stored")
|
| 337 |
+
results[file_path.name] = len(result)
|
| 338 |
+
else:
|
| 339 |
+
print(f"❌ Failed: {result}")
|
| 340 |
+
results[file_path.name] = "ERROR"
|
| 341 |
+
|
| 342 |
+
# Summary
|
| 343 |
+
print(f"\n📊 BATCH PROCESSING SUMMARY")
|
| 344 |
+
print(f"{'='*40}")
|
| 345 |
+
successful = sum(1 for result in results.values() if isinstance(result, int))
|
| 346 |
+
total = len(results)
|
| 347 |
+
|
| 348 |
+
for filename, result in results.items():
|
| 349 |
+
status = f"{result} chunks" if isinstance(result, int) else result
|
| 350 |
+
print(f"{filename}: {status}")
|
| 351 |
+
|
| 352 |
+
print(f"\n✅ Successfully processed: {successful}/{total} files")
|
| 353 |
+
|
| 354 |
+
# Test search with all documents
|
| 355 |
+
print(f"\n🔍 Testing search with all processed documents...")
|
| 356 |
+
test_search_functionality(vector_db, "batch_processed")
|
| 357 |
+
|
| 358 |
+
if __name__ == "__main__":
|
| 359 |
+
print("Choose workflow:")
|
| 360 |
+
print("1. Single file processing")
|
| 361 |
+
print("2. Batch directory processing")
|
| 362 |
+
|
| 363 |
+
workflow_choice = input("Enter choice (1 or 2): ").strip()
|
| 364 |
+
|
| 365 |
+
if workflow_choice == "1":
|
| 366 |
+
main()
|
| 367 |
+
elif workflow_choice == "2":
|
| 368 |
+
batch_process_directory()
|
| 369 |
+
else:
|
| 370 |
+
print("❌ Invalid choice")
|
test_hackathon_format.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for hackathon API format
|
| 4 |
+
Matches the exact requirements from the hackathon
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
# Your ngrok URL
|
| 11 |
+
BASE_URL = "https://0468c638cef7.ngrok-free.app"
|
| 12 |
+
|
| 13 |
+
def test_hackathon_format():
|
| 14 |
+
"""Test the hackathon API format"""
|
| 15 |
+
print("🧪 Testing Hackathon API Format...")
|
| 16 |
+
|
| 17 |
+
url = f"{BASE_URL}/hackrx/run"
|
| 18 |
+
|
| 19 |
+
# Headers as required by hackathon
|
| 20 |
+
headers = {
|
| 21 |
+
"Content-Type": "application/json",
|
| 22 |
+
"Accept": "application/json",
|
| 23 |
+
"Authorization": "Bearer e6dfe3fbc81eaccabae37a2960e15bc85abe1d3e710777adbbae47ee6b4b4fae" # Any API key works for testing
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
# Request body as required by hackathon
|
| 27 |
+
payload = {
|
| 28 |
+
"documents": "https://hackrx.blob.core.windows.net/assets/policy.pdf?sv=2023-01-03&st=2025-07-04T09%3A11%3A24Z&se=2027-07-05T09%3A11%3A00Z&sr=b&sp=r&sig=N4a9OU0w0QXO6AOIBiu4bpl7AXvEZogeT%2FjUHNO7HzQ%3D",
|
| 29 |
+
"questions": [
|
| 30 |
+
"What is the grace period for premium payment under the National Parivar Mediclaim Plus Policy?",
|
| 31 |
+
"What is the waiting period for pre-existing diseases (PED) to be covered?",
|
| 32 |
+
"Does this policy cover maternity expenses, and what are the conditions?",
|
| 33 |
+
"What is the waiting period for cataract surgery?",
|
| 34 |
+
"Are the medical expenses for an organ donor covered under this policy?"
|
| 35 |
+
]
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
print(f"URL: {url}")
|
| 40 |
+
print(f"Headers: {headers}")
|
| 41 |
+
print(f"Payload: {json.dumps(payload, indent=2)}")
|
| 42 |
+
|
| 43 |
+
response = requests.post(
|
| 44 |
+
url,
|
| 45 |
+
json=payload,
|
| 46 |
+
headers=headers,
|
| 47 |
+
timeout=60 # 60 second timeout
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
print(f"Status Code: {response.status_code}")
|
| 51 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 52 |
+
|
| 53 |
+
if response.status_code == 200:
|
| 54 |
+
print("✅ Hackathon API format test passed!")
|
| 55 |
+
return True
|
| 56 |
+
else:
|
| 57 |
+
print("❌ Hackathon API format test failed")
|
| 58 |
+
return False
|
| 59 |
+
|
| 60 |
+
except requests.exceptions.ConnectionError:
|
| 61 |
+
print("❌ Connection error - server might not be running")
|
| 62 |
+
return False
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"❌ Error: {e}")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
def test_simple_format():
|
| 68 |
+
"""Test with simple questions (no document URL)"""
|
| 69 |
+
print("\n🧪 Testing Simple Format (No Document URL)...")
|
| 70 |
+
|
| 71 |
+
url = f"{BASE_URL}/hackrx/run"
|
| 72 |
+
|
| 73 |
+
headers = {
|
| 74 |
+
"Content-Type": "application/json",
|
| 75 |
+
"Accept": "application/json",
|
| 76 |
+
"Authorization": "Bearer test_key_123"
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
payload = {
|
| 80 |
+
"questions": [
|
| 81 |
+
"What is covered under this policy?",
|
| 82 |
+
"What is the maximum coverage amount?"
|
| 83 |
+
]
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
response = requests.post(
|
| 88 |
+
url,
|
| 89 |
+
json=payload,
|
| 90 |
+
headers=headers,
|
| 91 |
+
timeout=30
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
print(f"Status Code: {response.status_code}")
|
| 95 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 96 |
+
|
| 97 |
+
if response.status_code == 200:
|
| 98 |
+
print("✅ Simple format test passed!")
|
| 99 |
+
return True
|
| 100 |
+
else:
|
| 101 |
+
print("❌ Simple format test failed")
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"❌ Error: {e}")
|
| 106 |
+
return False
|
| 107 |
+
|
| 108 |
+
def main():
|
| 109 |
+
"""Run all tests"""
|
| 110 |
+
print("🚀 Testing Hackathon API Format")
|
| 111 |
+
print("=" * 60)
|
| 112 |
+
|
| 113 |
+
# Test simple format first
|
| 114 |
+
simple_ok = test_simple_format()
|
| 115 |
+
|
| 116 |
+
# Test full hackathon format
|
| 117 |
+
hackathon_ok = test_hackathon_format()
|
| 118 |
+
|
| 119 |
+
print("\n" + "=" * 60)
|
| 120 |
+
print("📊 TEST RESULTS")
|
| 121 |
+
print("=" * 60)
|
| 122 |
+
print(f"Simple Format: {'✅ PASS' if simple_ok else '❌ FAIL'}")
|
| 123 |
+
print(f"Hackathon Format: {'✅ PASS' if hackathon_ok else '❌ FAIL'}")
|
| 124 |
+
|
| 125 |
+
if hackathon_ok:
|
| 126 |
+
print("\n🎉 Your API is ready for hackathon submission!")
|
| 127 |
+
print(f"URL: {BASE_URL}/hackrx/run")
|
| 128 |
+
print("\n📋 Submission Details:")
|
| 129 |
+
print(f"Webhook URL: {BASE_URL}/hackrx/run")
|
| 130 |
+
print("Method: POST")
|
| 131 |
+
print("Headers: Authorization: Bearer <api_key>")
|
| 132 |
+
print("Content-Type: application/json")
|
| 133 |
+
else:
|
| 134 |
+
print("\n⚠️ Please fix the issues and try again.")
|
| 135 |
+
|
| 136 |
+
if __name__ == "__main__":
|
| 137 |
+
main()
|
test_hackrx.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for hackrx/run endpoint
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import requests
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
# Your ngrok URL
|
| 10 |
+
BASE_URL = "https://c1c8ea4c476e.ngrok-free.app"
|
| 11 |
+
|
| 12 |
+
def test_hackrx_run():
|
| 13 |
+
"""Test the hackrx/run endpoint"""
|
| 14 |
+
print("🧪 Testing hackrx/run endpoint...")
|
| 15 |
+
|
| 16 |
+
url = f"{BASE_URL}/hackrx/run"
|
| 17 |
+
payload = {
|
| 18 |
+
"questions": [
|
| 19 |
+
"What is covered under this policy?",
|
| 20 |
+
"What is the maximum coverage amount?"
|
| 21 |
+
]
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
print(f"URL: {url}")
|
| 26 |
+
print(f"Payload: {json.dumps(payload, indent=2)}")
|
| 27 |
+
|
| 28 |
+
response = requests.post(
|
| 29 |
+
url,
|
| 30 |
+
json=payload,
|
| 31 |
+
headers={"Content-Type": "application/json"},
|
| 32 |
+
timeout=30
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
print(f"Status Code: {response.status_code}")
|
| 36 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
| 37 |
+
|
| 38 |
+
if response.status_code == 200:
|
| 39 |
+
print("✅ hackrx/run endpoint is working!")
|
| 40 |
+
return True
|
| 41 |
+
else:
|
| 42 |
+
print("❌ hackrx/run endpoint failed")
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
except requests.exceptions.ConnectionError:
|
| 46 |
+
print("❌ Connection error - server might not be running")
|
| 47 |
+
return False
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"❌ Error: {e}")
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
def test_health():
|
| 53 |
+
"""Test health endpoint"""
|
| 54 |
+
print("\n🔍 Testing health endpoint...")
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
response = requests.get(f"{BASE_URL}/api/health")
|
| 58 |
+
print(f"Health Status: {response.status_code}")
|
| 59 |
+
print(f"Health Response: {json.dumps(response.json(), indent=2)}")
|
| 60 |
+
return response.status_code == 200
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f"❌ Health check failed: {e}")
|
| 63 |
+
return False
|
| 64 |
+
|
| 65 |
+
def test_root():
|
| 66 |
+
"""Test root endpoint"""
|
| 67 |
+
print("\n🏠 Testing root endpoint...")
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
response = requests.get(f"{BASE_URL}/")
|
| 71 |
+
print(f"Root Status: {response.status_code}")
|
| 72 |
+
print(f"Root Response: {json.dumps(response.json(), indent=2)}")
|
| 73 |
+
return response.status_code == 200
|
| 74 |
+
except Exception as e:
|
| 75 |
+
print(f"❌ Root check failed: {e}")
|
| 76 |
+
return False
|
| 77 |
+
|
| 78 |
+
def main():
|
| 79 |
+
"""Run all tests"""
|
| 80 |
+
print("🚀 Testing HackRX Endpoints")
|
| 81 |
+
print("=" * 50)
|
| 82 |
+
|
| 83 |
+
# Test basic endpoints first
|
| 84 |
+
health_ok = test_health()
|
| 85 |
+
root_ok = test_root()
|
| 86 |
+
|
| 87 |
+
if not health_ok:
|
| 88 |
+
print("❌ Server is not responding. Please restart the Flask server.")
|
| 89 |
+
return
|
| 90 |
+
|
| 91 |
+
# Test the main hackrx/run endpoint
|
| 92 |
+
hackrx_ok = test_hackrx_run()
|
| 93 |
+
|
| 94 |
+
print("\n" + "=" * 50)
|
| 95 |
+
print("📊 TEST RESULTS")
|
| 96 |
+
print("=" * 50)
|
| 97 |
+
print(f"Health Check: {'✅ PASS' if health_ok else '❌ FAIL'}")
|
| 98 |
+
print(f"Root Endpoint: {'✅ PASS' if root_ok else '❌ FAIL'}")
|
| 99 |
+
print(f"HackRX Run: {'✅ PASS' if hackrx_ok else '❌ FAIL'}")
|
| 100 |
+
|
| 101 |
+
if hackrx_ok:
|
| 102 |
+
print("\n🎉 Your endpoint is ready for hackathon submission!")
|
| 103 |
+
print(f"URL: {BASE_URL}/hackrx/run")
|
| 104 |
+
else:
|
| 105 |
+
print("\n⚠️ Please restart your Flask server and try again.")
|
| 106 |
+
|
| 107 |
+
if __name__ == "__main__":
|
| 108 |
+
main()
|
test_integrated_system.py
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Integrated System Test: Query Parser + Vector Database + LLM Reasoning
|
| 3 |
+
Tests the complete workflow from query parsing to reasoning
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import tempfile
|
| 9 |
+
import shutil
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
def test_integrated_system():
|
| 13 |
+
"""Test the complete integrated system workflow"""
|
| 14 |
+
print("🚀 Integrated System Test")
|
| 15 |
+
print("="*50)
|
| 16 |
+
print("Testing: Query Parser → Vector Database → LLM Reasoning")
|
| 17 |
+
print("="*50)
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
# Import all components
|
| 21 |
+
print("🔄 Importing components...")
|
| 22 |
+
from query_parser import AdvancedQueryParser
|
| 23 |
+
from vector_database import VectorDatabase
|
| 24 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 25 |
+
print("✅ All components imported successfully")
|
| 26 |
+
|
| 27 |
+
# Initialize components
|
| 28 |
+
print("\n🔄 Initializing components...")
|
| 29 |
+
|
| 30 |
+
# Initialize query parser
|
| 31 |
+
query_parser = AdvancedQueryParser(use_gpu=False)
|
| 32 |
+
print("✅ Query parser initialized")
|
| 33 |
+
|
| 34 |
+
# Initialize vector database
|
| 35 |
+
vector_db = VectorDatabase(
|
| 36 |
+
collection_name="test_policy_docs",
|
| 37 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 38 |
+
persist_directory="./test_vector_db"
|
| 39 |
+
)
|
| 40 |
+
print("✅ Vector database initialized")
|
| 41 |
+
|
| 42 |
+
# Initialize LLM reasoning (with fallback for missing model)
|
| 43 |
+
try:
|
| 44 |
+
reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
|
| 45 |
+
llm_available = True
|
| 46 |
+
print("✅ LLM reasoning engine initialized")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"⚠️ LLM reasoning not available: {e}")
|
| 49 |
+
llm_available = False
|
| 50 |
+
|
| 51 |
+
# Create test documents
|
| 52 |
+
print("\n🔄 Creating test documents...")
|
| 53 |
+
test_docs = create_test_documents()
|
| 54 |
+
|
| 55 |
+
# Store documents in vector database
|
| 56 |
+
print("🔄 Storing documents in vector database...")
|
| 57 |
+
for doc in test_docs:
|
| 58 |
+
vector_db.add_document(
|
| 59 |
+
content=doc['content'],
|
| 60 |
+
metadata={
|
| 61 |
+
'source_file': doc['filename'],
|
| 62 |
+
'doc_type': 'policy_section',
|
| 63 |
+
'section': doc['section']
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
print(f"✅ Stored {len(test_docs)} documents")
|
| 67 |
+
|
| 68 |
+
# Test queries
|
| 69 |
+
test_queries = [
|
| 70 |
+
"Is heart surgery covered?",
|
| 71 |
+
"What's the waiting period for claims?",
|
| 72 |
+
"How much coverage do I have for dental treatment?",
|
| 73 |
+
"What documents do I need to file a claim?",
|
| 74 |
+
"Are pre-existing conditions covered?"
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
print(f"\n🔄 Testing {len(test_queries)} queries...")
|
| 78 |
+
|
| 79 |
+
results = []
|
| 80 |
+
for i, query in enumerate(test_queries, 1):
|
| 81 |
+
print(f"\n--- Query {i}: {query} ---")
|
| 82 |
+
|
| 83 |
+
# Step 1: Parse query
|
| 84 |
+
print("🔄 Step 1: Parsing query...")
|
| 85 |
+
parsed_query = query_parser.parse_query(query)
|
| 86 |
+
print(f" Query Type: {parsed_query.query_type}")
|
| 87 |
+
print(f" Intent: {parsed_query.intent}")
|
| 88 |
+
print(f" Entities: {list(parsed_query.entities.keys())}")
|
| 89 |
+
print(f" Keywords: {parsed_query.keywords[:5]}")
|
| 90 |
+
|
| 91 |
+
# Step 2: Search vector database
|
| 92 |
+
print("🔄 Step 2: Searching vector database...")
|
| 93 |
+
search_results = vector_db.search_documents(
|
| 94 |
+
query=query,
|
| 95 |
+
n_results=3,
|
| 96 |
+
similarity_threshold=0.1 # Lower threshold for better matching
|
| 97 |
+
)
|
| 98 |
+
print(f" Found {len(search_results)} relevant documents")
|
| 99 |
+
|
| 100 |
+
# Step 3: LLM reasoning (if available)
|
| 101 |
+
if llm_available:
|
| 102 |
+
print("🔄 Step 3: LLM reasoning...")
|
| 103 |
+
|
| 104 |
+
# Use search results if available, otherwise use fallback context
|
| 105 |
+
if search_results:
|
| 106 |
+
context = search_results
|
| 107 |
+
else:
|
| 108 |
+
# Create fallback context based on query type
|
| 109 |
+
context = [{
|
| 110 |
+
'content': f"Based on the query '{query}', this appears to be a {parsed_query.query_type} inquiry.",
|
| 111 |
+
'source_file': 'fallback_context',
|
| 112 |
+
'similarity_score': 0.5
|
| 113 |
+
}]
|
| 114 |
+
|
| 115 |
+
reasoning_result = reasoning_engine.analyze_query(
|
| 116 |
+
query=query,
|
| 117 |
+
context=context,
|
| 118 |
+
query_type=parsed_query.query_type
|
| 119 |
+
)
|
| 120 |
+
print(f" Decision: {reasoning_result.decision}")
|
| 121 |
+
print(f" Confidence: {reasoning_result.confidence_score:.2f}")
|
| 122 |
+
print(f" Justification: {reasoning_result.justification[:100]}...")
|
| 123 |
+
|
| 124 |
+
# Validate reasoning result
|
| 125 |
+
is_valid = reasoning_engine.validate_decision(reasoning_result)
|
| 126 |
+
print(f" Valid Result: {'✅' if is_valid else '❌'}")
|
| 127 |
+
|
| 128 |
+
results.append({
|
| 129 |
+
'query': query,
|
| 130 |
+
'parsed': parsed_query,
|
| 131 |
+
'search_results': search_results,
|
| 132 |
+
'reasoning': reasoning_result,
|
| 133 |
+
'valid': is_valid
|
| 134 |
+
})
|
| 135 |
+
else:
|
| 136 |
+
print("🔄 Step 3: LLM reasoning (not available)")
|
| 137 |
+
results.append({
|
| 138 |
+
'query': query,
|
| 139 |
+
'parsed': parsed_query,
|
| 140 |
+
'search_results': search_results,
|
| 141 |
+
'reasoning': None,
|
| 142 |
+
'valid': False
|
| 143 |
+
})
|
| 144 |
+
|
| 145 |
+
# Generate summary report
|
| 146 |
+
print(f"\n{'='*50}")
|
| 147 |
+
print("📊 INTEGRATION TEST RESULTS")
|
| 148 |
+
print(f"{'='*50}")
|
| 149 |
+
|
| 150 |
+
successful_queries = sum(1 for r in results if r['valid'])
|
| 151 |
+
total_queries = len(results)
|
| 152 |
+
|
| 153 |
+
print(f"Total Queries Tested: {total_queries}")
|
| 154 |
+
print(f"Successful Reasoning: {successful_queries}")
|
| 155 |
+
print(f"Success Rate: {successful_queries/total_queries*100:.1f}%")
|
| 156 |
+
|
| 157 |
+
# Detailed results
|
| 158 |
+
print(f"\n📋 DETAILED RESULTS:")
|
| 159 |
+
for i, result in enumerate(results, 1):
|
| 160 |
+
status = "✅" if result['valid'] else "⚠️"
|
| 161 |
+
print(f"{i}. {status} {result['query']}")
|
| 162 |
+
if result['reasoning']:
|
| 163 |
+
print(f" Decision: {result['reasoning'].decision}")
|
| 164 |
+
print(f" Confidence: {result['reasoning'].confidence_score:.2f}")
|
| 165 |
+
|
| 166 |
+
# Test specific functionality
|
| 167 |
+
print(f"\n🧪 FUNCTIONALITY TESTS:")
|
| 168 |
+
|
| 169 |
+
# Test 1: Query parsing
|
| 170 |
+
print("🔄 Test 1: Query parsing functionality...")
|
| 171 |
+
test_parsing()
|
| 172 |
+
|
| 173 |
+
# Test 2: Vector search
|
| 174 |
+
print("🔄 Test 2: Vector search functionality...")
|
| 175 |
+
test_vector_search(vector_db)
|
| 176 |
+
|
| 177 |
+
# Test 3: LLM reasoning (if available)
|
| 178 |
+
if llm_available:
|
| 179 |
+
print("🔄 Test 3: LLM reasoning functionality...")
|
| 180 |
+
test_reasoning(reasoning_engine)
|
| 181 |
+
|
| 182 |
+
# Cleanup
|
| 183 |
+
print(f"\n🧹 Cleaning up...")
|
| 184 |
+
cleanup_test_data()
|
| 185 |
+
|
| 186 |
+
print(f"\n🎉 Integration test completed!")
|
| 187 |
+
return True
|
| 188 |
+
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"❌ Integration test failed: {e}")
|
| 191 |
+
import traceback
|
| 192 |
+
traceback.print_exc()
|
| 193 |
+
return False
|
| 194 |
+
|
| 195 |
+
def create_test_documents():
|
| 196 |
+
"""Create test insurance policy documents"""
|
| 197 |
+
docs = [
|
| 198 |
+
{
|
| 199 |
+
'filename': 'coverage_policy.txt',
|
| 200 |
+
'section': 'coverage',
|
| 201 |
+
'content': '''
|
| 202 |
+
MEDICAL COVERAGE POLICY
|
| 203 |
+
|
| 204 |
+
This policy provides comprehensive medical coverage including:
|
| 205 |
+
- Heart surgery and cardiac procedures: Up to $50,000
|
| 206 |
+
- Dental treatment: Up to $2,000 annually
|
| 207 |
+
- Prescription medications: 80% coverage
|
| 208 |
+
- Hospital stays: Up to $1,000 per day
|
| 209 |
+
- Specialist consultations: $100 per visit
|
| 210 |
+
|
| 211 |
+
WAITING PERIODS:
|
| 212 |
+
- General medical: 30 days
|
| 213 |
+
- Pre-existing conditions: 12 months
|
| 214 |
+
- Dental procedures: 6 months
|
| 215 |
+
- Major surgeries: 90 days
|
| 216 |
+
|
| 217 |
+
EXCLUSIONS:
|
| 218 |
+
- Cosmetic procedures
|
| 219 |
+
- Experimental treatments
|
| 220 |
+
- Injuries from dangerous activities
|
| 221 |
+
- Pre-existing conditions (first 12 months)
|
| 222 |
+
'''
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
'filename': 'claim_process.txt',
|
| 226 |
+
'section': 'claims',
|
| 227 |
+
'content': '''
|
| 228 |
+
CLAIM PROCESSING PROCEDURES
|
| 229 |
+
|
| 230 |
+
To file a claim, you must provide:
|
| 231 |
+
1. Completed claim form
|
| 232 |
+
2. Medical certificate from doctor
|
| 233 |
+
3. Original receipts and bills
|
| 234 |
+
4. Prescription details (if applicable)
|
| 235 |
+
5. Hospital discharge summary (if hospitalized)
|
| 236 |
+
|
| 237 |
+
PROCESSING TIMES:
|
| 238 |
+
- Standard claims: 10-15 business days
|
| 239 |
+
- Urgent claims: 3-5 business days
|
| 240 |
+
- Complex cases: 20-30 business days
|
| 241 |
+
|
| 242 |
+
CLAIM LIMITS:
|
| 243 |
+
- Maximum annual benefit: $100,000
|
| 244 |
+
- Maximum per claim: $25,000
|
| 245 |
+
- Deductible: $500 per year
|
| 246 |
+
'''
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
'filename': 'policy_terms.txt',
|
| 250 |
+
'section': 'terms',
|
| 251 |
+
'content': '''
|
| 252 |
+
POLICY TERMS AND CONDITIONS
|
| 253 |
+
|
| 254 |
+
ELIGIBILITY:
|
| 255 |
+
- Age 18-65 years
|
| 256 |
+
- No pre-existing conditions (first year)
|
| 257 |
+
- Must be employed or have alternative coverage
|
| 258 |
+
|
| 259 |
+
COVERAGE PERIOD:
|
| 260 |
+
- Policy term: 12 months
|
| 261 |
+
- Renewable annually
|
| 262 |
+
- Grace period: 30 days for premium payment
|
| 263 |
+
|
| 264 |
+
CANCELLATION:
|
| 265 |
+
- 30 days written notice required
|
| 266 |
+
- Pro-rated refund for unused period
|
| 267 |
+
- No refund after claim submission
|
| 268 |
+
|
| 269 |
+
DISPUTE RESOLUTION:
|
| 270 |
+
- Internal review process
|
| 271 |
+
- External arbitration available
|
| 272 |
+
- 60-day response time for appeals
|
| 273 |
+
'''
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
'filename': 'dental_coverage.txt',
|
| 277 |
+
'section': 'dental',
|
| 278 |
+
'content': '''
|
| 279 |
+
DENTAL COVERAGE DETAILS
|
| 280 |
+
|
| 281 |
+
Dental procedures covered:
|
| 282 |
+
- Routine cleanings: 100% coverage
|
| 283 |
+
- Fillings and basic procedures: 80% coverage
|
| 284 |
+
- Root canals: 70% coverage
|
| 285 |
+
- Crowns and bridges: 50% coverage
|
| 286 |
+
- Annual limit: $2,000
|
| 287 |
+
|
| 288 |
+
Waiting period: 6 months for major procedures
|
| 289 |
+
Pre-existing conditions: Not covered for first 12 months
|
| 290 |
+
'''
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
'filename': 'waiting_periods.txt',
|
| 294 |
+
'section': 'waiting_periods',
|
| 295 |
+
'content': '''
|
| 296 |
+
WAITING PERIODS AND TIMELINES
|
| 297 |
+
|
| 298 |
+
General Medical Coverage:
|
| 299 |
+
- Waiting period: 30 days
|
| 300 |
+
- Coverage begins after 30 days of policy start
|
| 301 |
+
|
| 302 |
+
Pre-existing Conditions:
|
| 303 |
+
- Waiting period: 12 months
|
| 304 |
+
- No coverage for first 12 months of policy
|
| 305 |
+
|
| 306 |
+
Dental Procedures:
|
| 307 |
+
- Basic procedures: 6 months waiting period
|
| 308 |
+
- Major procedures: 12 months waiting period
|
| 309 |
+
|
| 310 |
+
Major Surgeries:
|
| 311 |
+
- Waiting period: 90 days
|
| 312 |
+
- Pre-authorization required
|
| 313 |
+
'''
|
| 314 |
+
}
|
| 315 |
+
]
|
| 316 |
+
return docs
|
| 317 |
+
|
| 318 |
+
def test_parsing():
|
| 319 |
+
"""Test query parsing functionality"""
|
| 320 |
+
try:
|
| 321 |
+
from query_parser import AdvancedQueryParser
|
| 322 |
+
|
| 323 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 324 |
+
|
| 325 |
+
test_cases = [
|
| 326 |
+
("Is heart surgery covered?", "medical_coverage"),
|
| 327 |
+
("How do I file a claim?", "claim_inquiry"),
|
| 328 |
+
("What's the waiting period?", "coverage_check"),
|
| 329 |
+
("Are dental procedures covered?", "medical_coverage")
|
| 330 |
+
]
|
| 331 |
+
|
| 332 |
+
passed = 0
|
| 333 |
+
for query, expected_type in test_cases:
|
| 334 |
+
parsed = parser.parse_query(query)
|
| 335 |
+
if parsed.query_type == expected_type or parsed.confidence > 0.3:
|
| 336 |
+
passed += 1
|
| 337 |
+
print(f" ✅ {query}")
|
| 338 |
+
else:
|
| 339 |
+
print(f" ❌ {query} (got {parsed.query_type})")
|
| 340 |
+
|
| 341 |
+
print(f" Parsing Test: {passed}/{len(test_cases)} passed")
|
| 342 |
+
|
| 343 |
+
except Exception as e:
|
| 344 |
+
print(f" ❌ Parsing test failed: {e}")
|
| 345 |
+
|
| 346 |
+
def test_vector_search(vector_db):
|
| 347 |
+
"""Test vector search functionality"""
|
| 348 |
+
try:
|
| 349 |
+
# Test basic search with lower threshold
|
| 350 |
+
results = vector_db.search_documents("heart surgery", n_results=2, similarity_threshold=0.05)
|
| 351 |
+
if results:
|
| 352 |
+
print(f" ✅ Vector search working ({len(results)} results)")
|
| 353 |
+
else:
|
| 354 |
+
print(f" ⚠️ Vector search returned no results")
|
| 355 |
+
|
| 356 |
+
# Test similarity threshold
|
| 357 |
+
results = vector_db.search_documents("dental treatment", n_results=5, similarity_threshold=0.05)
|
| 358 |
+
print(f" ✅ Similarity threshold test ({len(results)} results)")
|
| 359 |
+
|
| 360 |
+
except Exception as e:
|
| 361 |
+
print(f" ❌ Vector search test failed: {e}")
|
| 362 |
+
|
| 363 |
+
def test_reasoning(reasoning_engine):
|
| 364 |
+
"""Test LLM reasoning functionality"""
|
| 365 |
+
try:
|
| 366 |
+
test_context = [
|
| 367 |
+
{
|
| 368 |
+
'content': 'Heart surgery is covered up to $50,000 with 90-day waiting period.',
|
| 369 |
+
'source_file': 'test.pdf',
|
| 370 |
+
'similarity_score': 0.9
|
| 371 |
+
}
|
| 372 |
+
]
|
| 373 |
+
|
| 374 |
+
result = reasoning_engine.analyze_query(
|
| 375 |
+
"Is heart surgery covered?",
|
| 376 |
+
test_context,
|
| 377 |
+
'coverage_check'
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
if result.decision in ['approved', 'denied', 'pending']:
|
| 381 |
+
print(f" ✅ Reasoning working (Decision: {result.decision})")
|
| 382 |
+
else:
|
| 383 |
+
print(f" ⚠️ Unexpected decision: {result.decision}")
|
| 384 |
+
|
| 385 |
+
# Test explanation
|
| 386 |
+
explanation = reasoning_engine.explain_decision(result)
|
| 387 |
+
if len(explanation) > 50:
|
| 388 |
+
print(f" ✅ Explanation generation working")
|
| 389 |
+
else:
|
| 390 |
+
print(f" ⚠️ Short explanation: {len(explanation)} chars")
|
| 391 |
+
|
| 392 |
+
except Exception as e:
|
| 393 |
+
print(f" ❌ Reasoning test failed: {e}")
|
| 394 |
+
|
| 395 |
+
def cleanup_test_data():
|
| 396 |
+
"""Clean up test data"""
|
| 397 |
+
try:
|
| 398 |
+
import time
|
| 399 |
+
import gc
|
| 400 |
+
|
| 401 |
+
# Force garbage collection to release file handles
|
| 402 |
+
gc.collect()
|
| 403 |
+
time.sleep(2) # Give more time for file handles to close
|
| 404 |
+
|
| 405 |
+
# Remove test vector database
|
| 406 |
+
if os.path.exists("./test_vector_db"):
|
| 407 |
+
try:
|
| 408 |
+
shutil.rmtree("./test_vector_db", ignore_errors=True)
|
| 409 |
+
print(" ✅ Test vector database cleaned")
|
| 410 |
+
except Exception as e:
|
| 411 |
+
print(f" ⚠️ Could not clean test vector database: {e}")
|
| 412 |
+
|
| 413 |
+
# Remove any temporary files
|
| 414 |
+
temp_files = [f for f in os.listdir('.') if f.startswith('temp_')]
|
| 415 |
+
for file in temp_files:
|
| 416 |
+
try:
|
| 417 |
+
os.remove(file)
|
| 418 |
+
print(f" ✅ Removed {file}")
|
| 419 |
+
except Exception as e:
|
| 420 |
+
print(f" ⚠️ Could not remove {file}: {e}")
|
| 421 |
+
|
| 422 |
+
# Try to remove any remaining test directories
|
| 423 |
+
test_dirs = ["./temp_test_db", "./integration_test_db", "./quick_test_db"]
|
| 424 |
+
for dir_path in test_dirs:
|
| 425 |
+
if os.path.exists(dir_path):
|
| 426 |
+
try:
|
| 427 |
+
shutil.rmtree(dir_path, ignore_errors=True)
|
| 428 |
+
print(f" ✅ Cleaned {dir_path}")
|
| 429 |
+
except Exception as e:
|
| 430 |
+
print(f" ⚠️ Could not clean {dir_path}: {e}")
|
| 431 |
+
|
| 432 |
+
except Exception as e:
|
| 433 |
+
print(f" ⚠️ Cleanup warning: {e}")
|
| 434 |
+
|
| 435 |
+
def test_individual_components():
|
| 436 |
+
"""Test individual components separately"""
|
| 437 |
+
print("\n🧪 INDIVIDUAL COMPONENT TESTS")
|
| 438 |
+
print("="*40)
|
| 439 |
+
|
| 440 |
+
# Test Query Parser
|
| 441 |
+
print("\n1️⃣ Testing Query Parser...")
|
| 442 |
+
try:
|
| 443 |
+
from query_parser import AdvancedQueryParser
|
| 444 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 445 |
+
|
| 446 |
+
test_query = "Is heart surgery covered under my policy?"
|
| 447 |
+
parsed = parser.parse_query(test_query)
|
| 448 |
+
|
| 449 |
+
print(f" ✅ Query parsing: {parsed.query_type}")
|
| 450 |
+
print(f" ✅ Entities found: {len(parsed.entities)}")
|
| 451 |
+
print(f" ✅ Keywords: {len(parsed.keywords)}")
|
| 452 |
+
|
| 453 |
+
except Exception as e:
|
| 454 |
+
print(f" ❌ Query parser test failed: {e}")
|
| 455 |
+
|
| 456 |
+
# Test Vector Database
|
| 457 |
+
print("\n2️⃣ Testing Vector Database...")
|
| 458 |
+
try:
|
| 459 |
+
from vector_database import VectorDatabase
|
| 460 |
+
|
| 461 |
+
# Create temporary database
|
| 462 |
+
temp_db = VectorDatabase(
|
| 463 |
+
collection_name="temp_test",
|
| 464 |
+
embedding_model="all-MiniLM-L6-v2",
|
| 465 |
+
persist_directory="./temp_test_db"
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
# Add test document
|
| 469 |
+
temp_db.add_document(
|
| 470 |
+
content="Heart surgery is covered up to $50,000.",
|
| 471 |
+
metadata={'source': 'test', 'type': 'coverage'}
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
# Search
|
| 475 |
+
results = temp_db.search_documents("heart surgery", n_results=1)
|
| 476 |
+
if results:
|
| 477 |
+
print(f" ✅ Vector database: {len(results)} results")
|
| 478 |
+
else:
|
| 479 |
+
print(f" ⚠️ Vector database: No results")
|
| 480 |
+
|
| 481 |
+
# Cleanup
|
| 482 |
+
if os.path.exists("./temp_test_db"):
|
| 483 |
+
shutil.rmtree("./temp_test_db")
|
| 484 |
+
|
| 485 |
+
except Exception as e:
|
| 486 |
+
print(f" ❌ Vector database test failed: {e}")
|
| 487 |
+
|
| 488 |
+
# Test LLM Reasoning
|
| 489 |
+
print("\n3️⃣ Testing LLM Reasoning...")
|
| 490 |
+
try:
|
| 491 |
+
from llm_reasoning import AdvancedLLMReasoning
|
| 492 |
+
|
| 493 |
+
reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
|
| 494 |
+
|
| 495 |
+
test_context = [
|
| 496 |
+
{
|
| 497 |
+
'content': 'Heart surgery is covered up to $50,000.',
|
| 498 |
+
'source_file': 'test.pdf',
|
| 499 |
+
'similarity_score': 0.9
|
| 500 |
+
}
|
| 501 |
+
]
|
| 502 |
+
|
| 503 |
+
result = reasoning_engine.analyze_query(
|
| 504 |
+
"Is heart surgery covered?",
|
| 505 |
+
test_context,
|
| 506 |
+
'coverage_check'
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
print(f" ✅ LLM reasoning: {result.decision}")
|
| 510 |
+
print(f" ✅ Confidence: {result.confidence_score:.2f}")
|
| 511 |
+
|
| 512 |
+
except Exception as e:
|
| 513 |
+
print(f" ❌ LLM reasoning test failed: {e}")
|
| 514 |
+
|
| 515 |
+
def main():
|
| 516 |
+
"""Main test runner"""
|
| 517 |
+
print("🚀 Integrated System Test Suite")
|
| 518 |
+
print("="*50)
|
| 519 |
+
|
| 520 |
+
# Test individual components first
|
| 521 |
+
test_individual_components()
|
| 522 |
+
|
| 523 |
+
# Test full integration
|
| 524 |
+
print(f"\n{'='*50}")
|
| 525 |
+
print("🔄 RUNNING FULL INTEGRATION TEST")
|
| 526 |
+
print(f"{'='*50}")
|
| 527 |
+
|
| 528 |
+
success = test_integrated_system()
|
| 529 |
+
|
| 530 |
+
if success:
|
| 531 |
+
print(f"\n🎉 All tests completed successfully!")
|
| 532 |
+
print("✅ Query Parser → Vector Database → LLM Reasoning integration working")
|
| 533 |
+
else:
|
| 534 |
+
print(f"\n⚠️ Some tests failed. Check the output above for details.")
|
| 535 |
+
|
| 536 |
+
print(f"\n💡 Next steps:")
|
| 537 |
+
print(" 1. Install missing dependencies if any")
|
| 538 |
+
print(" 2. Download required model files")
|
| 539 |
+
print(" 3. Adjust configuration parameters")
|
| 540 |
+
print(" 4. Run with your actual documents")
|
| 541 |
+
|
| 542 |
+
if __name__ == "__main__":
|
| 543 |
+
main()
|
test_llm_reasoning.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
test_pdf_processor.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PDF Document Processor Test
|
| 3 |
+
Allows you to choose any PDF file and process it with the document processor
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import tkinter as tk
|
| 9 |
+
from tkinter import filedialog, messagebox
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import tempfile
|
| 12 |
+
import shutil
|
| 13 |
+
|
| 14 |
+
def select_pdf_file():
|
| 15 |
+
"""Open file dialog to select a PDF file"""
|
| 16 |
+
root = tk.Tk()
|
| 17 |
+
root.withdraw() # Hide the main window
|
| 18 |
+
|
| 19 |
+
file_path = filedialog.askopenfilename(
|
| 20 |
+
title="Select a PDF file to process",
|
| 21 |
+
filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
root.destroy()
|
| 25 |
+
return file_path
|
| 26 |
+
|
| 27 |
+
def process_pdf_with_ocr(pdf_path, use_ocr=False):
|
| 28 |
+
"""Process a PDF file with optional OCR"""
|
| 29 |
+
try:
|
| 30 |
+
from document_processer import AdvancedDocumentProcessor
|
| 31 |
+
|
| 32 |
+
print(f"🔄 Processing PDF: {pdf_path}")
|
| 33 |
+
print(f"📄 File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
|
| 34 |
+
|
| 35 |
+
# Initialize processor
|
| 36 |
+
processor = AdvancedDocumentProcessor()
|
| 37 |
+
|
| 38 |
+
# Process the document
|
| 39 |
+
chunks = processor.process_document(pdf_path, use_ocr=use_ocr)
|
| 40 |
+
|
| 41 |
+
return chunks, None
|
| 42 |
+
|
| 43 |
+
except Exception as e:
|
| 44 |
+
return None, str(e)
|
| 45 |
+
|
| 46 |
+
def display_results(chunks, pdf_path):
|
| 47 |
+
"""Display processing results"""
|
| 48 |
+
print(f"\n{'='*60}")
|
| 49 |
+
print("📊 PROCESSING RESULTS")
|
| 50 |
+
print(f"{'='*60}")
|
| 51 |
+
|
| 52 |
+
print(f"📄 PDF File: {pdf_path}")
|
| 53 |
+
print(f"📊 Total Chunks: {len(chunks)}")
|
| 54 |
+
|
| 55 |
+
# Analyze chunks
|
| 56 |
+
text_chunks = [c for c in chunks if c.section_type == 'main_text']
|
| 57 |
+
table_chunks = [c for c in chunks if c.section_type == 'table']
|
| 58 |
+
metadata_chunks = [c for c in chunks if c.section_type == 'metadata']
|
| 59 |
+
|
| 60 |
+
print(f"📝 Text Chunks: {len(text_chunks)}")
|
| 61 |
+
print(f"📊 Table Chunks: {len(table_chunks)}")
|
| 62 |
+
print(f"🏷️ Metadata Chunks: {len(metadata_chunks)}")
|
| 63 |
+
|
| 64 |
+
# Show sample chunks
|
| 65 |
+
print(f"\n📋 SAMPLE CHUNKS:")
|
| 66 |
+
for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
|
| 67 |
+
print(f"\nChunk {i+1}:")
|
| 68 |
+
print(f" ID: {chunk.chunk_id}")
|
| 69 |
+
print(f" Type: {chunk.section_type}")
|
| 70 |
+
print(f" Content Preview: {chunk.content[:150]}...")
|
| 71 |
+
|
| 72 |
+
if chunk.table_data:
|
| 73 |
+
print(f" Table Data: {len(chunk.table_data.get('data', []))} rows")
|
| 74 |
+
|
| 75 |
+
if len(chunks) > 5:
|
| 76 |
+
print(f"\n... and {len(chunks) - 5} more chunks")
|
| 77 |
+
|
| 78 |
+
# Save results to file
|
| 79 |
+
save_results_to_file(chunks, pdf_path)
|
| 80 |
+
|
| 81 |
+
def save_results_to_file(chunks, pdf_path):
|
| 82 |
+
"""Save processing results to a text file"""
|
| 83 |
+
try:
|
| 84 |
+
# Create output filename
|
| 85 |
+
pdf_name = Path(pdf_path).stem
|
| 86 |
+
output_file = f"{pdf_name}_processed_results.txt"
|
| 87 |
+
|
| 88 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 89 |
+
f.write(f"PDF Processing Results\n")
|
| 90 |
+
f.write(f"="*50 + "\n")
|
| 91 |
+
f.write(f"Source PDF: {pdf_path}\n")
|
| 92 |
+
f.write(f"Total Chunks: {len(chunks)}\n\n")
|
| 93 |
+
|
| 94 |
+
for i, chunk in enumerate(chunks):
|
| 95 |
+
f.write(f"Chunk {i+1}:\n")
|
| 96 |
+
f.write(f" ID: {chunk.chunk_id}\n")
|
| 97 |
+
f.write(f" Type: {chunk.section_type}\n")
|
| 98 |
+
f.write(f" Source: {chunk.source_file}\n")
|
| 99 |
+
f.write(f" File Type: {chunk.file_type}\n")
|
| 100 |
+
f.write(f" Content:\n{chunk.content}\n")
|
| 101 |
+
f.write(f" {'-'*40}\n\n")
|
| 102 |
+
|
| 103 |
+
print(f"\n💾 Results saved to: {output_file}")
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"⚠️ Could not save results to file: {e}")
|
| 107 |
+
|
| 108 |
+
def analyze_pdf_content(chunks):
|
| 109 |
+
"""Analyze the content of processed chunks"""
|
| 110 |
+
print(f"\n🔍 CONTENT ANALYSIS")
|
| 111 |
+
print(f"{'='*40}")
|
| 112 |
+
|
| 113 |
+
total_text_length = sum(len(chunk.content) for chunk in chunks)
|
| 114 |
+
avg_chunk_size = total_text_length / len(chunks) if chunks else 0
|
| 115 |
+
|
| 116 |
+
print(f"📏 Total Text Length: {total_text_length:,} characters")
|
| 117 |
+
print(f"📊 Average Chunk Size: {avg_chunk_size:.0f} characters")
|
| 118 |
+
|
| 119 |
+
# Find longest and shortest chunks
|
| 120 |
+
if chunks:
|
| 121 |
+
longest_chunk = max(chunks, key=lambda x: len(x.content))
|
| 122 |
+
shortest_chunk = min(chunks, key=lambda x: len(x.content))
|
| 123 |
+
|
| 124 |
+
print(f"📏 Longest Chunk: {len(longest_chunk.content)} characters")
|
| 125 |
+
print(f"📏 Shortest Chunk: {len(shortest_chunk.content)} characters")
|
| 126 |
+
|
| 127 |
+
# Count unique words
|
| 128 |
+
all_text = " ".join(chunk.content for chunk in chunks)
|
| 129 |
+
unique_words = len(set(all_text.lower().split()))
|
| 130 |
+
total_words = len(all_text.split())
|
| 131 |
+
|
| 132 |
+
print(f"📝 Total Words: {total_words:,}")
|
| 133 |
+
print(f"📝 Unique Words: {unique_words:,}")
|
| 134 |
+
|
| 135 |
+
def main():
|
| 136 |
+
"""Main function to run the PDF processor test"""
|
| 137 |
+
print("🚀 PDF Document Processor Test")
|
| 138 |
+
print("="*50)
|
| 139 |
+
print("This tool allows you to process any PDF file by specifying its path.")
|
| 140 |
+
print("You can choose whether to use OCR for better text extraction.")
|
| 141 |
+
print()
|
| 142 |
+
|
| 143 |
+
# Check if document processor is available
|
| 144 |
+
try:
|
| 145 |
+
from document_processer import AdvancedDocumentProcessor
|
| 146 |
+
print("✅ Document processor loaded successfully")
|
| 147 |
+
except ImportError as e:
|
| 148 |
+
print(f"❌ Error loading document processor: {e}")
|
| 149 |
+
print("💡 Make sure document_processer.py is in the same directory")
|
| 150 |
+
return
|
| 151 |
+
|
| 152 |
+
# Get PDF file path
|
| 153 |
+
print("\n📁 Enter the path to your PDF file:")
|
| 154 |
+
print(" Examples:")
|
| 155 |
+
print(" - C:\\Users\\YourName\\Documents\\document.pdf")
|
| 156 |
+
print(" - /home/username/documents/document.pdf")
|
| 157 |
+
print(" - ./local_file.pdf")
|
| 158 |
+
print(" - Or press Enter to use file dialog")
|
| 159 |
+
|
| 160 |
+
pdf_path = input("PDF file path: ").strip()
|
| 161 |
+
|
| 162 |
+
# If no path provided, use file dialog
|
| 163 |
+
if not pdf_path:
|
| 164 |
+
print("\n📁 Opening file dialog...")
|
| 165 |
+
pdf_path = select_pdf_file()
|
| 166 |
+
|
| 167 |
+
if not pdf_path:
|
| 168 |
+
print("❌ No file selected. Exiting.")
|
| 169 |
+
return
|
| 170 |
+
|
| 171 |
+
# Expand relative paths and resolve to absolute path
|
| 172 |
+
pdf_path = os.path.abspath(os.path.expanduser(pdf_path))
|
| 173 |
+
|
| 174 |
+
if not os.path.exists(pdf_path):
|
| 175 |
+
print(f"❌ File not found: {pdf_path}")
|
| 176 |
+
print("💡 Please check the file path and try again.")
|
| 177 |
+
return
|
| 178 |
+
|
| 179 |
+
# Check if it's actually a PDF file
|
| 180 |
+
if not pdf_path.lower().endswith('.pdf'):
|
| 181 |
+
print(f"⚠️ Warning: File doesn't have .pdf extension: {pdf_path}")
|
| 182 |
+
proceed = input("Continue anyway? (y/n): ").lower().strip()
|
| 183 |
+
if proceed not in ['y', 'yes']:
|
| 184 |
+
print("❌ Exiting.")
|
| 185 |
+
return
|
| 186 |
+
|
| 187 |
+
print(f"✅ Found file: {pdf_path}")
|
| 188 |
+
print(f"📄 File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
|
| 189 |
+
|
| 190 |
+
# Ask about OCR
|
| 191 |
+
print("\n🤔 Do you want to use OCR for better text extraction?")
|
| 192 |
+
print(" OCR is useful for scanned PDFs or PDFs with images")
|
| 193 |
+
print(" OCR takes longer but provides better results for image-based PDFs")
|
| 194 |
+
|
| 195 |
+
use_ocr = input("Use OCR? (y/n): ").lower().strip() in ['y', 'yes']
|
| 196 |
+
|
| 197 |
+
if use_ocr:
|
| 198 |
+
print("🔍 Will use OCR for text extraction")
|
| 199 |
+
else:
|
| 200 |
+
print("📝 Will use standard text extraction")
|
| 201 |
+
|
| 202 |
+
# Process the PDF
|
| 203 |
+
print(f"\n🔄 Processing PDF...")
|
| 204 |
+
chunks, error = process_pdf_with_ocr(pdf_path, use_ocr)
|
| 205 |
+
|
| 206 |
+
if error:
|
| 207 |
+
print(f"❌ Error processing PDF: {error}")
|
| 208 |
+
print("\n💡 Troubleshooting tips:")
|
| 209 |
+
print("1. Make sure the PDF file is not corrupted")
|
| 210 |
+
print("2. Try without OCR if the PDF has text")
|
| 211 |
+
print("3. Check if all dependencies are installed")
|
| 212 |
+
print("4. Verify the file path is correct")
|
| 213 |
+
return
|
| 214 |
+
|
| 215 |
+
if not chunks:
|
| 216 |
+
print("❌ No chunks were extracted from the PDF")
|
| 217 |
+
print("💡 This might be because:")
|
| 218 |
+
print(" - The PDF is password protected")
|
| 219 |
+
print(" - The PDF contains only images")
|
| 220 |
+
print(" - The PDF is corrupted")
|
| 221 |
+
return
|
| 222 |
+
|
| 223 |
+
# Display results
|
| 224 |
+
display_results(chunks, pdf_path)
|
| 225 |
+
|
| 226 |
+
# Analyze content
|
| 227 |
+
analyze_pdf_content(chunks)
|
| 228 |
+
|
| 229 |
+
print(f"\n🎉 PDF processing completed successfully!")
|
| 230 |
+
print(f"📄 Processed: {pdf_path}")
|
| 231 |
+
print(f"📊 Extracted: {len(chunks)} chunks")
|
| 232 |
+
|
| 233 |
+
def batch_process_pdfs():
|
| 234 |
+
"""Process multiple PDF files in a directory"""
|
| 235 |
+
print("🔄 Batch PDF Processing")
|
| 236 |
+
print("="*40)
|
| 237 |
+
|
| 238 |
+
# Select directory
|
| 239 |
+
root = tk.Tk()
|
| 240 |
+
root.withdraw()
|
| 241 |
+
directory = filedialog.askdirectory(title="Select directory containing PDF files")
|
| 242 |
+
root.destroy()
|
| 243 |
+
|
| 244 |
+
if not directory:
|
| 245 |
+
print("❌ No directory selected")
|
| 246 |
+
return
|
| 247 |
+
|
| 248 |
+
# Find PDF files
|
| 249 |
+
pdf_files = list(Path(directory).glob("*.pdf"))
|
| 250 |
+
|
| 251 |
+
if not pdf_files:
|
| 252 |
+
print("❌ No PDF files found in the selected directory")
|
| 253 |
+
return
|
| 254 |
+
|
| 255 |
+
print(f"📁 Found {len(pdf_files)} PDF files in {directory}")
|
| 256 |
+
|
| 257 |
+
# Process each PDF
|
| 258 |
+
results = {}
|
| 259 |
+
for pdf_file in pdf_files:
|
| 260 |
+
print(f"\n🔄 Processing: {pdf_file.name}")
|
| 261 |
+
chunks, error = process_pdf_with_ocr(str(pdf_file), use_ocr=False)
|
| 262 |
+
|
| 263 |
+
if error:
|
| 264 |
+
print(f"❌ Error: {error}")
|
| 265 |
+
results[pdf_file.name] = "ERROR"
|
| 266 |
+
else:
|
| 267 |
+
print(f"✅ Processed: {len(chunks)} chunks")
|
| 268 |
+
results[pdf_file.name] = len(chunks)
|
| 269 |
+
|
| 270 |
+
# Summary
|
| 271 |
+
print(f"\n📊 BATCH PROCESSING SUMMARY")
|
| 272 |
+
print(f"{'='*40}")
|
| 273 |
+
successful = sum(1 for result in results.values() if isinstance(result, int))
|
| 274 |
+
total = len(results)
|
| 275 |
+
|
| 276 |
+
for filename, result in results.items():
|
| 277 |
+
status = f"{result} chunks" if isinstance(result, int) else result
|
| 278 |
+
print(f"{filename}: {status}")
|
| 279 |
+
|
| 280 |
+
print(f"\n✅ Successfully processed: {successful}/{total} files")
|
| 281 |
+
|
| 282 |
+
def process_from_command_line():
|
| 283 |
+
"""Process PDF from command line arguments"""
|
| 284 |
+
import sys
|
| 285 |
+
|
| 286 |
+
if len(sys.argv) < 2:
|
| 287 |
+
print("❌ Usage: python test_pdf_processor.py <pdf_file_path> [--ocr]")
|
| 288 |
+
print(" Example: python test_pdf_processor.py C:\\path\\to\\document.pdf --ocr")
|
| 289 |
+
return
|
| 290 |
+
|
| 291 |
+
pdf_path = sys.argv[1]
|
| 292 |
+
use_ocr = "--ocr" in sys.argv
|
| 293 |
+
|
| 294 |
+
# Expand relative paths and resolve to absolute path
|
| 295 |
+
pdf_path = os.path.abspath(os.path.expanduser(pdf_path))
|
| 296 |
+
|
| 297 |
+
if not os.path.exists(pdf_path):
|
| 298 |
+
print(f"❌ File not found: {pdf_path}")
|
| 299 |
+
return
|
| 300 |
+
|
| 301 |
+
print(f"🚀 Processing PDF from command line: {pdf_path}")
|
| 302 |
+
print(f"🔍 OCR enabled: {use_ocr}")
|
| 303 |
+
|
| 304 |
+
# Process the PDF
|
| 305 |
+
chunks, error = process_pdf_with_ocr(pdf_path, use_ocr)
|
| 306 |
+
|
| 307 |
+
if error:
|
| 308 |
+
print(f"❌ Error processing PDF: {error}")
|
| 309 |
+
return
|
| 310 |
+
|
| 311 |
+
if not chunks:
|
| 312 |
+
print("❌ No chunks were extracted from the PDF")
|
| 313 |
+
return
|
| 314 |
+
|
| 315 |
+
# Display results
|
| 316 |
+
display_results(chunks, pdf_path)
|
| 317 |
+
analyze_pdf_content(chunks)
|
| 318 |
+
|
| 319 |
+
print(f"\n🎉 PDF processing completed successfully!")
|
| 320 |
+
|
| 321 |
+
if __name__ == "__main__":
|
| 322 |
+
# Check if command line arguments are provided
|
| 323 |
+
if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
|
| 324 |
+
process_from_command_line()
|
| 325 |
+
else:
|
| 326 |
+
print("Choose an option:")
|
| 327 |
+
print("1. Process a single PDF file")
|
| 328 |
+
print("2. Batch process all PDFs in a directory")
|
| 329 |
+
print("3. Process from command line (usage: python test_pdf_processor.py <pdf_path> [--ocr])")
|
| 330 |
+
|
| 331 |
+
choice = input("Enter choice (1, 2, or 3): ").strip()
|
| 332 |
+
|
| 333 |
+
if choice == "1":
|
| 334 |
+
main()
|
| 335 |
+
elif choice == "2":
|
| 336 |
+
batch_process_pdfs()
|
| 337 |
+
elif choice == "3":
|
| 338 |
+
print("\nCommand line usage:")
|
| 339 |
+
print("python test_pdf_processor.py <pdf_file_path> [--ocr]")
|
| 340 |
+
print("\nExamples:")
|
| 341 |
+
print("python test_pdf_processor.py C:\\path\\to\\document.pdf")
|
| 342 |
+
print("python test_pdf_processor.py /home/user/document.pdf --ocr")
|
| 343 |
+
print("python test_pdf_processor.py ./local_file.pdf")
|
| 344 |
+
else:
|
| 345 |
+
print("❌ Invalid choice. Exiting.")
|
test_query_parser.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test file for Query Parser
|
| 3 |
+
Checks if query_parser.py is working correctly with various test cases
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
|
| 10 |
+
def test_query_parser_import():
|
| 11 |
+
"""Test if query parser can be imported without errors"""
|
| 12 |
+
print("🧪 Testing Query Parser Import")
|
| 13 |
+
print("="*40)
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from query_parser import AdvancedQueryParser, ParsedQuery, QueryEntity
|
| 17 |
+
print("✅ Query parser imported successfully")
|
| 18 |
+
return True
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"❌ Error importing query parser: {e}")
|
| 21 |
+
import traceback
|
| 22 |
+
traceback.print_exc()
|
| 23 |
+
return False
|
| 24 |
+
|
| 25 |
+
def test_query_parser_initialization():
|
| 26 |
+
"""Test if query parser can be initialized"""
|
| 27 |
+
print("\n🧪 Testing Query Parser Initialization")
|
| 28 |
+
print("="*50)
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
from query_parser import AdvancedQueryParser
|
| 32 |
+
|
| 33 |
+
# Test initialization with default parameters
|
| 34 |
+
print("🔄 Initializing query parser...")
|
| 35 |
+
parser = AdvancedQueryParser(use_gpu=False) # Use CPU for testing
|
| 36 |
+
print("✅ Query parser initialized successfully")
|
| 37 |
+
|
| 38 |
+
# Test basic attributes
|
| 39 |
+
print("🔄 Checking parser attributes...")
|
| 40 |
+
assert hasattr(parser, 'lemmatizer'), "Lemmatizer not found"
|
| 41 |
+
assert hasattr(parser, 'stop_words'), "Stop words not found"
|
| 42 |
+
assert hasattr(parser, 'insurance_entities'), "Insurance entities not found"
|
| 43 |
+
assert hasattr(parser, 'query_types'), "Query types not found"
|
| 44 |
+
print("✅ All required attributes present")
|
| 45 |
+
|
| 46 |
+
return True
|
| 47 |
+
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"❌ Error initializing query parser: {e}")
|
| 50 |
+
import traceback
|
| 51 |
+
traceback.print_exc()
|
| 52 |
+
return False
|
| 53 |
+
|
| 54 |
+
def test_basic_query_parsing():
|
| 55 |
+
"""Test basic query parsing functionality"""
|
| 56 |
+
print("\n🧪 Testing Basic Query Parsing")
|
| 57 |
+
print("="*40)
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
from query_parser import AdvancedQueryParser
|
| 61 |
+
|
| 62 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 63 |
+
|
| 64 |
+
# Test queries
|
| 65 |
+
test_queries = [
|
| 66 |
+
"Is heart surgery covered?",
|
| 67 |
+
"How do I file a claim?",
|
| 68 |
+
"What's the waiting period?",
|
| 69 |
+
"Can I claim for dental treatment?",
|
| 70 |
+
"What documents are needed?"
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
results = []
|
| 74 |
+
for i, query in enumerate(test_queries):
|
| 75 |
+
print(f"\n📝 Test Query {i+1}: {query}")
|
| 76 |
+
|
| 77 |
+
try:
|
| 78 |
+
parsed = parser.parse_query(query)
|
| 79 |
+
|
| 80 |
+
# Check if parsed query has required attributes
|
| 81 |
+
assert hasattr(parsed, 'original_query'), "Missing original_query"
|
| 82 |
+
assert hasattr(parsed, 'enhanced_query'), "Missing enhanced_query"
|
| 83 |
+
assert hasattr(parsed, 'query_type'), "Missing query_type"
|
| 84 |
+
assert hasattr(parsed, 'entities'), "Missing entities"
|
| 85 |
+
assert hasattr(parsed, 'intent'), "Missing intent"
|
| 86 |
+
assert hasattr(parsed, 'confidence'), "Missing confidence"
|
| 87 |
+
assert hasattr(parsed, 'keywords'), "Missing keywords"
|
| 88 |
+
assert hasattr(parsed, 'synonyms'), "Missing synonyms"
|
| 89 |
+
assert hasattr(parsed, 'context'), "Missing context"
|
| 90 |
+
assert hasattr(parsed, 'timestamp'), "Missing timestamp"
|
| 91 |
+
|
| 92 |
+
print(f" ✅ Parsed successfully")
|
| 93 |
+
print(f" 📊 Type: {parsed.query_type}")
|
| 94 |
+
print(f" 🎯 Intent: {parsed.intent}")
|
| 95 |
+
print(f" 📈 Confidence: {parsed.confidence:.2f}")
|
| 96 |
+
print(f" 🔑 Keywords: {parsed.keywords[:3]}")
|
| 97 |
+
print(f" 🏷️ Entities: {list(parsed.entities.keys())}")
|
| 98 |
+
|
| 99 |
+
results.append(True)
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f" ❌ Failed to parse: {e}")
|
| 103 |
+
results.append(False)
|
| 104 |
+
|
| 105 |
+
success_count = sum(results)
|
| 106 |
+
total_count = len(results)
|
| 107 |
+
|
| 108 |
+
print(f"\n📊 Basic Parsing Results: {success_count}/{total_count} successful")
|
| 109 |
+
return success_count == total_count
|
| 110 |
+
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"❌ Error in basic query parsing: {e}")
|
| 113 |
+
return False
|
| 114 |
+
|
| 115 |
+
def test_entity_extraction():
|
| 116 |
+
"""Test entity extraction functionality"""
|
| 117 |
+
print("\n🧪 Testing Entity Extraction")
|
| 118 |
+
print("="*40)
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
from query_parser import AdvancedQueryParser
|
| 122 |
+
|
| 123 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 124 |
+
|
| 125 |
+
# Test queries with specific entities
|
| 126 |
+
test_cases = [
|
| 127 |
+
{
|
| 128 |
+
'query': "Is heart surgery covered under my policy?",
|
| 129 |
+
'expected_entities': ['medical_condition', 'coverage_type']
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
'query': "Can I claim $5000 for dental treatment?",
|
| 133 |
+
'expected_entities': ['amount', 'medical_condition']
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
'query': "What's the 30-day waiting period for pre-existing conditions?",
|
| 137 |
+
'expected_entities': ['time_period']
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
'query': "Do I need a doctor's report for this claim?",
|
| 141 |
+
'expected_entities': ['document_type']
|
| 142 |
+
}
|
| 143 |
+
]
|
| 144 |
+
|
| 145 |
+
results = []
|
| 146 |
+
for i, test_case in enumerate(test_cases):
|
| 147 |
+
query = test_case['query']
|
| 148 |
+
expected_entities = test_case['expected_entities']
|
| 149 |
+
|
| 150 |
+
print(f"\n📝 Test Case {i+1}: {query}")
|
| 151 |
+
|
| 152 |
+
try:
|
| 153 |
+
parsed = parser.parse_query(query)
|
| 154 |
+
extracted_entities = list(parsed.entities.keys())
|
| 155 |
+
|
| 156 |
+
print(f" 🏷️ Extracted entities: {extracted_entities}")
|
| 157 |
+
print(f" 🎯 Expected entities: {expected_entities}")
|
| 158 |
+
|
| 159 |
+
# Check if any expected entities were found
|
| 160 |
+
found_entities = [entity for entity in expected_entities if entity in extracted_entities]
|
| 161 |
+
|
| 162 |
+
if found_entities:
|
| 163 |
+
print(f" ✅ Found expected entities: {found_entities}")
|
| 164 |
+
results.append(True)
|
| 165 |
+
else:
|
| 166 |
+
print(f" ⚠️ No expected entities found")
|
| 167 |
+
results.append(False)
|
| 168 |
+
|
| 169 |
+
except Exception as e:
|
| 170 |
+
print(f" ❌ Error: {e}")
|
| 171 |
+
results.append(False)
|
| 172 |
+
|
| 173 |
+
success_count = sum(results)
|
| 174 |
+
total_count = len(results)
|
| 175 |
+
|
| 176 |
+
print(f"\n📊 Entity Extraction Results: {success_count}/{total_count} successful")
|
| 177 |
+
return success_count > 0 # At least some entities should be found
|
| 178 |
+
|
| 179 |
+
except Exception as e:
|
| 180 |
+
print(f"❌ Error in entity extraction: {e}")
|
| 181 |
+
return False
|
| 182 |
+
|
| 183 |
+
def test_query_classification():
|
| 184 |
+
"""Test query type classification"""
|
| 185 |
+
print("\n🧪 Testing Query Classification")
|
| 186 |
+
print("="*40)
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
from query_parser import AdvancedQueryParser
|
| 190 |
+
|
| 191 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 192 |
+
|
| 193 |
+
# Test queries for different types
|
| 194 |
+
test_cases = [
|
| 195 |
+
{
|
| 196 |
+
'query': "How do I file a claim?",
|
| 197 |
+
'expected_type': 'claim_inquiry'
|
| 198 |
+
},
|
| 199 |
+
{
|
| 200 |
+
'query': "What is covered under my policy?",
|
| 201 |
+
'expected_type': 'coverage_check'
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
'query': "What are the policy terms?",
|
| 205 |
+
'expected_type': 'policy_review'
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
'query': "Is dental treatment covered?",
|
| 209 |
+
'expected_type': 'medical_coverage'
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
'query': "What is this document about?",
|
| 213 |
+
'expected_type': 'general_inquiry'
|
| 214 |
+
}
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
results = []
|
| 218 |
+
for i, test_case in enumerate(test_cases):
|
| 219 |
+
query = test_case['query']
|
| 220 |
+
expected_type = test_case['expected_type']
|
| 221 |
+
|
| 222 |
+
print(f"\n📝 Test Case {i+1}: {query}")
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
parsed = parser.parse_query(query)
|
| 226 |
+
actual_type = parsed.query_type
|
| 227 |
+
|
| 228 |
+
print(f" 🎯 Expected type: {expected_type}")
|
| 229 |
+
print(f" 📊 Actual type: {actual_type}")
|
| 230 |
+
print(f" 📈 Confidence: {parsed.confidence:.2f}")
|
| 231 |
+
|
| 232 |
+
if actual_type == expected_type:
|
| 233 |
+
print(f" ✅ Classification correct")
|
| 234 |
+
results.append(True)
|
| 235 |
+
else:
|
| 236 |
+
print(f" ⚠️ Classification mismatch")
|
| 237 |
+
results.append(False)
|
| 238 |
+
|
| 239 |
+
except Exception as e:
|
| 240 |
+
print(f" ❌ Error: {e}")
|
| 241 |
+
results.append(False)
|
| 242 |
+
|
| 243 |
+
success_count = sum(results)
|
| 244 |
+
total_count = len(results)
|
| 245 |
+
|
| 246 |
+
print(f"\n📊 Classification Results: {success_count}/{total_count} correct")
|
| 247 |
+
return success_count > 0 # At least some classifications should work
|
| 248 |
+
|
| 249 |
+
except Exception as e:
|
| 250 |
+
print(f"❌ Error in query classification: {e}")
|
| 251 |
+
return False
|
| 252 |
+
|
| 253 |
+
def test_query_suggestions():
|
| 254 |
+
"""Test query suggestion functionality"""
|
| 255 |
+
print("\n🧪 Testing Query Suggestions")
|
| 256 |
+
print("="*40)
|
| 257 |
+
|
| 258 |
+
try:
|
| 259 |
+
from query_parser import AdvancedQueryParser
|
| 260 |
+
|
| 261 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 262 |
+
|
| 263 |
+
# Test queries for suggestions
|
| 264 |
+
test_queries = [
|
| 265 |
+
"claim",
|
| 266 |
+
"coverage",
|
| 267 |
+
"medical",
|
| 268 |
+
"policy",
|
| 269 |
+
"documents"
|
| 270 |
+
]
|
| 271 |
+
|
| 272 |
+
results = []
|
| 273 |
+
for i, query in enumerate(test_queries):
|
| 274 |
+
print(f"\n📝 Test Query {i+1}: {query}")
|
| 275 |
+
|
| 276 |
+
try:
|
| 277 |
+
suggestions = parser.get_query_suggestions(query)
|
| 278 |
+
|
| 279 |
+
print(f" 💡 Suggestions: {len(suggestions)} found")
|
| 280 |
+
for j, suggestion in enumerate(suggestions[:2]):
|
| 281 |
+
print(f" {j+1}. {suggestion}")
|
| 282 |
+
|
| 283 |
+
if suggestions:
|
| 284 |
+
print(f" ✅ Suggestions generated successfully")
|
| 285 |
+
results.append(True)
|
| 286 |
+
else:
|
| 287 |
+
print(f" ⚠️ No suggestions generated")
|
| 288 |
+
results.append(False)
|
| 289 |
+
|
| 290 |
+
except Exception as e:
|
| 291 |
+
print(f" ❌ Error: {e}")
|
| 292 |
+
results.append(False)
|
| 293 |
+
|
| 294 |
+
success_count = sum(results)
|
| 295 |
+
total_count = len(results)
|
| 296 |
+
|
| 297 |
+
print(f"\n📊 Suggestion Results: {success_count}/{total_count} successful")
|
| 298 |
+
return success_count > 0 # At least some suggestions should work
|
| 299 |
+
|
| 300 |
+
except Exception as e:
|
| 301 |
+
print(f"❌ Error in query suggestions: {e}")
|
| 302 |
+
return False
|
| 303 |
+
|
| 304 |
+
def test_error_handling():
|
| 305 |
+
"""Test error handling with invalid inputs"""
|
| 306 |
+
print("\n🧪 Testing Error Handling")
|
| 307 |
+
print("="*40)
|
| 308 |
+
|
| 309 |
+
try:
|
| 310 |
+
from query_parser import AdvancedQueryParser
|
| 311 |
+
|
| 312 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 313 |
+
|
| 314 |
+
# Test with invalid inputs
|
| 315 |
+
invalid_inputs = [
|
| 316 |
+
"", # Empty string
|
| 317 |
+
" ", # Whitespace only
|
| 318 |
+
"a", # Single character
|
| 319 |
+
"123", # Numbers only
|
| 320 |
+
"!@#$%", # Special characters only
|
| 321 |
+
None # None value
|
| 322 |
+
]
|
| 323 |
+
|
| 324 |
+
results = []
|
| 325 |
+
for i, invalid_input in enumerate(invalid_inputs):
|
| 326 |
+
print(f"\n📝 Test Case {i+1}: {repr(invalid_input)}")
|
| 327 |
+
|
| 328 |
+
try:
|
| 329 |
+
parsed = parser.parse_query(invalid_input)
|
| 330 |
+
|
| 331 |
+
# Should return a basic parsed query
|
| 332 |
+
assert parsed is not None, "Should return a parsed query"
|
| 333 |
+
assert hasattr(parsed, 'original_query'), "Should have original_query"
|
| 334 |
+
assert hasattr(parsed, 'enhanced_query'), "Should have enhanced_query"
|
| 335 |
+
|
| 336 |
+
print(f" ✅ Handled gracefully")
|
| 337 |
+
results.append(True)
|
| 338 |
+
|
| 339 |
+
except Exception as e:
|
| 340 |
+
print(f" ❌ Error: {e}")
|
| 341 |
+
results.append(False)
|
| 342 |
+
|
| 343 |
+
success_count = sum(results)
|
| 344 |
+
total_count = len(results)
|
| 345 |
+
|
| 346 |
+
print(f"\n📊 Error Handling Results: {success_count}/{total_count} handled")
|
| 347 |
+
return success_count > 0 # At least some should be handled
|
| 348 |
+
|
| 349 |
+
except Exception as e:
|
| 350 |
+
print(f"❌ Error in error handling test: {e}")
|
| 351 |
+
return False
|
| 352 |
+
|
| 353 |
+
def test_comprehensive_workflow():
|
| 354 |
+
"""Test a comprehensive workflow with real-world queries"""
|
| 355 |
+
print("\n🧪 Testing Comprehensive Workflow")
|
| 356 |
+
print("="*50)
|
| 357 |
+
|
| 358 |
+
try:
|
| 359 |
+
from query_parser import AdvancedQueryParser
|
| 360 |
+
|
| 361 |
+
parser = AdvancedQueryParser(use_gpu=False)
|
| 362 |
+
|
| 363 |
+
# Real-world insurance queries
|
| 364 |
+
real_queries = [
|
| 365 |
+
"I need to file a claim for my recent heart surgery that cost $25,000",
|
| 366 |
+
"What's covered under my health insurance policy for dental procedures?",
|
| 367 |
+
"How long is the waiting period for pre-existing conditions?",
|
| 368 |
+
"Do I need a doctor's report and medical certificate for this claim?",
|
| 369 |
+
"Can I claim for prescription medications and hospital stays?"
|
| 370 |
+
]
|
| 371 |
+
|
| 372 |
+
results = []
|
| 373 |
+
for i, query in enumerate(real_queries):
|
| 374 |
+
print(f"\n📝 Real Query {i+1}: {query}")
|
| 375 |
+
|
| 376 |
+
try:
|
| 377 |
+
parsed = parser.parse_query(query)
|
| 378 |
+
|
| 379 |
+
print(f" 📊 Type: {parsed.query_type}")
|
| 380 |
+
print(f" 🎯 Intent: {parsed.intent}")
|
| 381 |
+
print(f" 📈 Confidence: {parsed.confidence:.2f}")
|
| 382 |
+
print(f" 🔑 Keywords: {parsed.keywords[:5]}")
|
| 383 |
+
print(f" 🏷️ Entities: {list(parsed.entities.keys())}")
|
| 384 |
+
|
| 385 |
+
# Check if enhanced query is different from original
|
| 386 |
+
if parsed.enhanced_query != parsed.original_query:
|
| 387 |
+
print(f" ✨ Query enhanced successfully")
|
| 388 |
+
|
| 389 |
+
# Check if we have meaningful results
|
| 390 |
+
if parsed.confidence > 0.0 or parsed.keywords or parsed.entities:
|
| 391 |
+
print(f" ✅ Meaningful results extracted")
|
| 392 |
+
results.append(True)
|
| 393 |
+
else:
|
| 394 |
+
print(f" ⚠️ Limited results")
|
| 395 |
+
results.append(False)
|
| 396 |
+
|
| 397 |
+
except Exception as e:
|
| 398 |
+
print(f" ❌ Error: {e}")
|
| 399 |
+
results.append(False)
|
| 400 |
+
|
| 401 |
+
success_count = sum(results)
|
| 402 |
+
total_count = len(results)
|
| 403 |
+
|
| 404 |
+
print(f"\n📊 Comprehensive Results: {success_count}/{total_count} successful")
|
| 405 |
+
return success_count > 0 # At least some should work
|
| 406 |
+
|
| 407 |
+
except Exception as e:
|
| 408 |
+
print(f"❌ Error in comprehensive workflow: {e}")
|
| 409 |
+
return False
|
| 410 |
+
|
| 411 |
+
def main():
|
| 412 |
+
"""Run all tests"""
|
| 413 |
+
print("🚀 Query Parser Test Suite")
|
| 414 |
+
print("="*60)
|
| 415 |
+
print("Testing query_parser.py functionality")
|
| 416 |
+
print()
|
| 417 |
+
|
| 418 |
+
# Run all tests
|
| 419 |
+
tests = [
|
| 420 |
+
("Import Test", test_query_parser_import),
|
| 421 |
+
("Initialization Test", test_query_parser_initialization),
|
| 422 |
+
("Basic Parsing Test", test_basic_query_parsing),
|
| 423 |
+
("Entity Extraction Test", test_entity_extraction),
|
| 424 |
+
("Query Classification Test", test_query_classification),
|
| 425 |
+
("Query Suggestions Test", test_query_suggestions),
|
| 426 |
+
("Error Handling Test", test_error_handling),
|
| 427 |
+
("Comprehensive Workflow Test", test_comprehensive_workflow)
|
| 428 |
+
]
|
| 429 |
+
|
| 430 |
+
results = {}
|
| 431 |
+
for test_name, test_func in tests:
|
| 432 |
+
print(f"\n{'='*60}")
|
| 433 |
+
print(f"🧪 Running {test_name}")
|
| 434 |
+
print(f"{'='*60}")
|
| 435 |
+
|
| 436 |
+
success = test_func()
|
| 437 |
+
results[test_name] = success
|
| 438 |
+
|
| 439 |
+
if success:
|
| 440 |
+
print(f"✅ {test_name} PASSED")
|
| 441 |
+
else:
|
| 442 |
+
print(f"❌ {test_name} FAILED")
|
| 443 |
+
|
| 444 |
+
# Summary
|
| 445 |
+
print(f"\n{'='*60}")
|
| 446 |
+
print("📊 TEST SUMMARY")
|
| 447 |
+
print(f"{'='*60}")
|
| 448 |
+
|
| 449 |
+
passed = sum(results.values())
|
| 450 |
+
total = len(results)
|
| 451 |
+
|
| 452 |
+
for test_name, success in results.items():
|
| 453 |
+
status = "✅ PASS" if success else "❌ FAIL"
|
| 454 |
+
print(f"{test_name:<30}: {status}")
|
| 455 |
+
|
| 456 |
+
print(f"\nOverall: {passed}/{total} tests passed")
|
| 457 |
+
|
| 458 |
+
if passed == total:
|
| 459 |
+
print("🎉 All tests passed! Your query parser is working correctly.")
|
| 460 |
+
elif passed > total // 2:
|
| 461 |
+
print("⚠️ Most tests passed, but some issues need attention.")
|
| 462 |
+
print("\n💡 Areas to check:")
|
| 463 |
+
for test_name, success in results.items():
|
| 464 |
+
if not success:
|
| 465 |
+
print(f" - {test_name}")
|
| 466 |
+
else:
|
| 467 |
+
print("❌ Many tests failed. Check the error messages above.")
|
| 468 |
+
print("\n💡 Common issues:")
|
| 469 |
+
print(" - Missing dependencies (NLTK, spaCy)")
|
| 470 |
+
print(" - Import errors")
|
| 471 |
+
print(" - Initialization problems")
|
| 472 |
+
|
| 473 |
+
if __name__ == "__main__":
|
| 474 |
+
main()
|
test_rag_system.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test RAG System Integration
|
| 3 |
+
Tests the complete flow: Document Processor → Vector Database → Query Parser → LLM Reasoning → RAG System
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import tempfile
|
| 8 |
+
import shutil
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def test_rag_system():
|
| 12 |
+
"""Test the complete RAG system workflow"""
|
| 13 |
+
print("🚀 RAG System Integration Test")
|
| 14 |
+
print("="*50)
|
| 15 |
+
print("Testing: Document Processor → Vector Database → Query Parser → LLM Reasoning → RAG System")
|
| 16 |
+
print("="*50)
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
# Import RAG system
|
| 20 |
+
print("🔄 Importing RAG system...")
|
| 21 |
+
from rag_system import AdvancedRAGSystem
|
| 22 |
+
print("✅ RAG system imported successfully")
|
| 23 |
+
|
| 24 |
+
# Initialize RAG system
|
| 25 |
+
print("🔄 Initializing RAG system...")
|
| 26 |
+
rag_system = AdvancedRAGSystem(
|
| 27 |
+
use_gpu=False, # Use CPU for testing
|
| 28 |
+
vector_db_path="./test_rag_db"
|
| 29 |
+
)
|
| 30 |
+
print("✅ RAG system initialized")
|
| 31 |
+
|
| 32 |
+
# Validate system
|
| 33 |
+
print("🔄 Validating system components...")
|
| 34 |
+
validation = rag_system.validate_system()
|
| 35 |
+
|
| 36 |
+
if validation['overall_status']:
|
| 37 |
+
print("✅ All components validated successfully")
|
| 38 |
+
else:
|
| 39 |
+
print("⚠️ Some components have issues:")
|
| 40 |
+
for error in validation['errors']:
|
| 41 |
+
print(f" - {error}")
|
| 42 |
+
|
| 43 |
+
# Create test documents
|
| 44 |
+
print("\n🔄 Creating test documents...")
|
| 45 |
+
test_docs = create_test_documents()
|
| 46 |
+
|
| 47 |
+
# Ingest documents
|
| 48 |
+
print("🔄 Ingesting documents...")
|
| 49 |
+
total_chunks = 0
|
| 50 |
+
for doc_info in test_docs:
|
| 51 |
+
try:
|
| 52 |
+
chunks = rag_system.ingest_document(doc_info['file_path'])
|
| 53 |
+
total_chunks += len(chunks)
|
| 54 |
+
print(f" ✅ Ingested {len(chunks)} chunks from {doc_info['name']}")
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f" ❌ Failed to ingest {doc_info['name']}: {e}")
|
| 57 |
+
|
| 58 |
+
print(f"✅ Total chunks ingested: {total_chunks}")
|
| 59 |
+
|
| 60 |
+
# Test queries
|
| 61 |
+
test_queries = [
|
| 62 |
+
"Is heart surgery covered under this policy?",
|
| 63 |
+
"What's the waiting period for dental procedures?",
|
| 64 |
+
"How do I file a claim?",
|
| 65 |
+
"What documents are needed for medical claims?",
|
| 66 |
+
"Are pre-existing conditions covered?"
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
print(f"\n🔄 Testing {len(test_queries)} queries...")
|
| 70 |
+
|
| 71 |
+
results = []
|
| 72 |
+
for i, query in enumerate(test_queries, 1):
|
| 73 |
+
print(f"\n--- Query {i}: {query} ---")
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# Process query through RAG system
|
| 77 |
+
result = rag_system.process_query(query, n_results=3)
|
| 78 |
+
|
| 79 |
+
print(f" Processing Time: {result.processing_time:.2f}s")
|
| 80 |
+
print(f" Query Type: {result.parsed_query.query_type}")
|
| 81 |
+
print(f" Intent: {result.parsed_query.intent}")
|
| 82 |
+
print(f" Confidence: {result.parsed_query.confidence:.2f}")
|
| 83 |
+
print(f" Search Results: {len(result.search_results)}")
|
| 84 |
+
print(f" Decision: {result.reasoning_result.decision}")
|
| 85 |
+
print(f" Reasoning Confidence: {result.reasoning_result.confidence_score:.2f}")
|
| 86 |
+
|
| 87 |
+
# Show top search result
|
| 88 |
+
if result.search_results:
|
| 89 |
+
top_result = result.search_results[0]
|
| 90 |
+
print(f" Top Result: {top_result.content[:100]}...")
|
| 91 |
+
print(f" Source: {top_result.source_file}")
|
| 92 |
+
print(f" Similarity: {top_result.similarity_score:.3f}")
|
| 93 |
+
|
| 94 |
+
# Show reasoning justification
|
| 95 |
+
if result.reasoning_result.justification:
|
| 96 |
+
print(f" Justification: {result.reasoning_result.justification[:150]}...")
|
| 97 |
+
|
| 98 |
+
results.append({
|
| 99 |
+
'query': query,
|
| 100 |
+
'result': result,
|
| 101 |
+
'success': True
|
| 102 |
+
})
|
| 103 |
+
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f" ❌ Query processing failed: {e}")
|
| 106 |
+
results.append({
|
| 107 |
+
'query': query,
|
| 108 |
+
'result': None,
|
| 109 |
+
'success': False,
|
| 110 |
+
'error': str(e)
|
| 111 |
+
})
|
| 112 |
+
|
| 113 |
+
# Generate summary report
|
| 114 |
+
print(f"\n{'='*50}")
|
| 115 |
+
print("📊 RAG SYSTEM TEST RESULTS")
|
| 116 |
+
print(f"{'='*50}")
|
| 117 |
+
|
| 118 |
+
successful_queries = sum(1 for r in results if r['success'])
|
| 119 |
+
total_queries = len(results)
|
| 120 |
+
|
| 121 |
+
print(f"Total Queries Tested: {total_queries}")
|
| 122 |
+
print(f"Successful Queries: {successful_queries}")
|
| 123 |
+
print(f"Success Rate: {successful_queries/total_queries*100:.1f}%")
|
| 124 |
+
|
| 125 |
+
# Detailed results
|
| 126 |
+
print(f"\n📋 DETAILED RESULTS:")
|
| 127 |
+
for i, result in enumerate(results, 1):
|
| 128 |
+
if result['success']:
|
| 129 |
+
rag_result = result['result']
|
| 130 |
+
status = "✅"
|
| 131 |
+
decision = rag_result.reasoning_result.decision
|
| 132 |
+
confidence = rag_result.reasoning_result.confidence_score
|
| 133 |
+
print(f"{i}. {status} {result['query']}")
|
| 134 |
+
print(f" Decision: {decision}")
|
| 135 |
+
print(f" Confidence: {confidence:.2f}")
|
| 136 |
+
print(f" Search Results: {len(rag_result.search_results)}")
|
| 137 |
+
else:
|
| 138 |
+
print(f"{i}. ❌ {result['query']}")
|
| 139 |
+
print(f" Error: {result['error']}")
|
| 140 |
+
|
| 141 |
+
# Test system statistics
|
| 142 |
+
print(f"\n📊 SYSTEM STATISTICS:")
|
| 143 |
+
stats = rag_system.get_system_statistics()
|
| 144 |
+
print(f" Vector Database: {stats.get('vector_database', {}).get('total_chunks', 0)} chunks")
|
| 145 |
+
print(f" Audit Trail: {stats.get('audit_trail', {}).get('total_entries', 0)} entries")
|
| 146 |
+
print(f" Successful Queries: {stats.get('audit_trail', {}).get('successful_queries', 0)}")
|
| 147 |
+
|
| 148 |
+
# Test audit trail
|
| 149 |
+
print(f"\n📋 AUDIT TRAIL SAMPLE:")
|
| 150 |
+
audit_trail = rag_system.get_audit_trail()
|
| 151 |
+
if audit_trail:
|
| 152 |
+
latest_entry = audit_trail[-1]
|
| 153 |
+
print(f" Latest Action: {latest_entry.get('action', 'unknown')}")
|
| 154 |
+
print(f" Status: {latest_entry.get('status', 'unknown')}")
|
| 155 |
+
print(f" Timestamp: {latest_entry.get('timestamp', 'unknown')}")
|
| 156 |
+
|
| 157 |
+
# Cleanup
|
| 158 |
+
print(f"\n🧹 Cleaning up...")
|
| 159 |
+
cleanup_test_data()
|
| 160 |
+
|
| 161 |
+
print(f"\n🎉 RAG system test completed!")
|
| 162 |
+
|
| 163 |
+
if successful_queries == total_queries:
|
| 164 |
+
print("✅ All queries processed successfully!")
|
| 165 |
+
print("🎯 RAG System is working perfectly!")
|
| 166 |
+
else:
|
| 167 |
+
print("⚠️ Some queries failed. Check the detailed results above.")
|
| 168 |
+
|
| 169 |
+
return successful_queries == total_queries
|
| 170 |
+
|
| 171 |
+
except Exception as e:
|
| 172 |
+
print(f"❌ RAG system test failed: {e}")
|
| 173 |
+
import traceback
|
| 174 |
+
traceback.print_exc()
|
| 175 |
+
return False
|
| 176 |
+
|
| 177 |
+
def create_test_documents():
|
| 178 |
+
"""Create test documents for RAG system"""
|
| 179 |
+
test_dir = tempfile.mkdtemp()
|
| 180 |
+
print(f"📁 Created test directory: {test_dir}")
|
| 181 |
+
|
| 182 |
+
docs = []
|
| 183 |
+
|
| 184 |
+
# Create policy document
|
| 185 |
+
policy_content = """
|
| 186 |
+
MEDICAL INSURANCE POLICY
|
| 187 |
+
|
| 188 |
+
COVERAGE DETAILS:
|
| 189 |
+
- Heart surgery: Covered up to $50,000
|
| 190 |
+
- Dental procedures: Covered up to $2,000 annually
|
| 191 |
+
- Prescription medications: 80% coverage
|
| 192 |
+
- Hospital stays: Up to $1,000 per day
|
| 193 |
+
- Specialist consultations: $100 per visit
|
| 194 |
+
|
| 195 |
+
WAITING PERIODS:
|
| 196 |
+
- General medical: 30 days
|
| 197 |
+
- Pre-existing conditions: 12 months
|
| 198 |
+
- Dental procedures: 6 months
|
| 199 |
+
- Major surgeries: 90 days
|
| 200 |
+
|
| 201 |
+
CLAIM PROCEDURES:
|
| 202 |
+
- Submit claim form within 30 days
|
| 203 |
+
- Include medical certificate
|
| 204 |
+
- Provide original receipts and bills
|
| 205 |
+
- Processing time: 10-15 business days
|
| 206 |
+
|
| 207 |
+
EXCLUSIONS:
|
| 208 |
+
- Cosmetic procedures
|
| 209 |
+
- Experimental treatments
|
| 210 |
+
- Injuries from dangerous activities
|
| 211 |
+
- Pre-existing conditions (first 12 months)
|
| 212 |
+
"""
|
| 213 |
+
|
| 214 |
+
policy_path = os.path.join(test_dir, "medical_policy.txt")
|
| 215 |
+
with open(policy_path, 'w', encoding='utf-8') as f:
|
| 216 |
+
f.write(policy_content)
|
| 217 |
+
|
| 218 |
+
docs.append({
|
| 219 |
+
'name': 'Medical Policy',
|
| 220 |
+
'file_path': policy_path,
|
| 221 |
+
'type': 'policy'
|
| 222 |
+
})
|
| 223 |
+
|
| 224 |
+
# Create claims guide
|
| 225 |
+
claims_content = """
|
| 226 |
+
CLAIMS PROCESSING GUIDE
|
| 227 |
+
|
| 228 |
+
REQUIRED DOCUMENTS:
|
| 229 |
+
1. Completed claim form
|
| 230 |
+
2. Medical certificate from doctor
|
| 231 |
+
3. Original receipts and bills
|
| 232 |
+
4. Prescription details (if applicable)
|
| 233 |
+
5. Hospital discharge summary (if hospitalized)
|
| 234 |
+
|
| 235 |
+
PROCESSING TIMES:
|
| 236 |
+
- Standard claims: 10-15 business days
|
| 237 |
+
- Urgent claims: 3-5 business days
|
| 238 |
+
- Complex cases: 20-30 business days
|
| 239 |
+
|
| 240 |
+
CLAIM LIMITS:
|
| 241 |
+
- Maximum annual benefit: $100,000
|
| 242 |
+
- Maximum per claim: $25,000
|
| 243 |
+
- Deductible: $500 per year
|
| 244 |
+
|
| 245 |
+
SUBMISSION METHODS:
|
| 246 |
+
- Online portal
|
| 247 |
+
- Mobile app
|
| 248 |
+
- Mail to claims department
|
| 249 |
+
- In-person at service centers
|
| 250 |
+
"""
|
| 251 |
+
|
| 252 |
+
claims_path = os.path.join(test_dir, "claims_guide.txt")
|
| 253 |
+
with open(claims_path, 'w', encoding='utf-8') as f:
|
| 254 |
+
f.write(claims_content)
|
| 255 |
+
|
| 256 |
+
docs.append({
|
| 257 |
+
'name': 'Claims Guide',
|
| 258 |
+
'file_path': claims_path,
|
| 259 |
+
'type': 'guide'
|
| 260 |
+
})
|
| 261 |
+
|
| 262 |
+
return docs
|
| 263 |
+
|
| 264 |
+
def cleanup_test_data():
|
| 265 |
+
"""Clean up test data"""
|
| 266 |
+
try:
|
| 267 |
+
import time
|
| 268 |
+
import gc
|
| 269 |
+
|
| 270 |
+
# Force garbage collection
|
| 271 |
+
gc.collect()
|
| 272 |
+
time.sleep(2)
|
| 273 |
+
|
| 274 |
+
# Remove test directories
|
| 275 |
+
test_dirs = ["./test_rag_db", "./test_vector_db", "./temp_test_db"]
|
| 276 |
+
for dir_path in test_dirs:
|
| 277 |
+
if os.path.exists(dir_path):
|
| 278 |
+
try:
|
| 279 |
+
shutil.rmtree(dir_path, ignore_errors=True)
|
| 280 |
+
print(f" ✅ Cleaned {dir_path}")
|
| 281 |
+
except Exception as e:
|
| 282 |
+
print(f" ⚠️ Could not clean {dir_path}: {e}")
|
| 283 |
+
|
| 284 |
+
# Remove temporary files
|
| 285 |
+
temp_files = [f for f in os.listdir('.') if f.startswith('temp_')]
|
| 286 |
+
for file in temp_files:
|
| 287 |
+
try:
|
| 288 |
+
os.remove(file)
|
| 289 |
+
print(f" ✅ Removed {file}")
|
| 290 |
+
except Exception as e:
|
| 291 |
+
print(f" ⚠️ Could not remove {file}: {e}")
|
| 292 |
+
|
| 293 |
+
except Exception as e:
|
| 294 |
+
print(f" ⚠️ Cleanup warning: {e}")
|
| 295 |
+
|
| 296 |
+
def test_individual_components():
|
| 297 |
+
"""Test individual components before RAG system"""
|
| 298 |
+
print("\n🧪 TESTING INDIVIDUAL COMPONENTS")
|
| 299 |
+
print("="*40)
|
| 300 |
+
|
| 301 |
+
components = {
|
| 302 |
+
'Document Processor': 'document_processer',
|
| 303 |
+
'Vector Database': 'vector_database',
|
| 304 |
+
'Query Parser': 'query_parser',
|
| 305 |
+
'LLM Reasoning': 'llm_reasoning'
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
results = {}
|
| 309 |
+
|
| 310 |
+
for name, module in components.items():
|
| 311 |
+
print(f"\n🔄 Testing {name}...")
|
| 312 |
+
try:
|
| 313 |
+
__import__(module)
|
| 314 |
+
print(f" ✅ {name} imported successfully")
|
| 315 |
+
results[name] = True
|
| 316 |
+
except Exception as e:
|
| 317 |
+
print(f" ❌ {name} import failed: {e}")
|
| 318 |
+
results[name] = False
|
| 319 |
+
|
| 320 |
+
# Summary
|
| 321 |
+
print(f"\n📊 COMPONENT TEST RESULTS:")
|
| 322 |
+
passed = sum(results.values())
|
| 323 |
+
total = len(results)
|
| 324 |
+
|
| 325 |
+
for name, result in results.items():
|
| 326 |
+
status = "✅ PASS" if result else "❌ FAIL"
|
| 327 |
+
print(f" {name}: {status}")
|
| 328 |
+
|
| 329 |
+
print(f"\nOverall: {passed}/{total} components ready")
|
| 330 |
+
|
| 331 |
+
return passed == total
|
| 332 |
+
|
| 333 |
+
def main():
|
| 334 |
+
"""Main test runner"""
|
| 335 |
+
print("🚀 RAG System Test Suite")
|
| 336 |
+
print("="*50)
|
| 337 |
+
|
| 338 |
+
# Test individual components first
|
| 339 |
+
components_ready = test_individual_components()
|
| 340 |
+
|
| 341 |
+
if not components_ready:
|
| 342 |
+
print("\n❌ Some components are not ready. Please fix the issues above.")
|
| 343 |
+
return False
|
| 344 |
+
|
| 345 |
+
print(f"\n{'='*50}")
|
| 346 |
+
print("🔄 RUNNING RAG SYSTEM INTEGRATION TEST")
|
| 347 |
+
print(f"{'='*50}")
|
| 348 |
+
|
| 349 |
+
# Test RAG system
|
| 350 |
+
success = test_rag_system()
|
| 351 |
+
|
| 352 |
+
if success:
|
| 353 |
+
print(f"\n🎉 RAG System Integration Test PASSED!")
|
| 354 |
+
print("✅ All components working together successfully")
|
| 355 |
+
print("🎯 Your RAG system is ready for production use!")
|
| 356 |
+
else:
|
| 357 |
+
print(f"\n⚠️ RAG System Integration Test FAILED!")
|
| 358 |
+
print("❌ Some issues need to be resolved")
|
| 359 |
+
|
| 360 |
+
print(f"\n💡 Next steps:")
|
| 361 |
+
print(" 1. Add your actual documents")
|
| 362 |
+
print(" 2. Customize the query processing")
|
| 363 |
+
print(" 3. Fine-tune the reasoning engine")
|
| 364 |
+
print(" 4. Deploy to production")
|
| 365 |
+
|
| 366 |
+
if __name__ == "__main__":
|
| 367 |
+
main()
|
test_upload.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test Upload Endpoint
|
| 4 |
+
This script tests the file upload functionality
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# Your ngrok URL
|
| 11 |
+
BASE_URL = "https://0468c638cef7.ngrok-free.app"
|
| 12 |
+
|
| 13 |
+
def test_upload():
|
| 14 |
+
"""Test file upload"""
|
| 15 |
+
print("🧪 Testing File Upload...")
|
| 16 |
+
|
| 17 |
+
# Check if we have a test file
|
| 18 |
+
test_files = ["doc2.pdf", "main.py", "app.py"]
|
| 19 |
+
test_file = None
|
| 20 |
+
|
| 21 |
+
for file in test_files:
|
| 22 |
+
if os.path.exists(file):
|
| 23 |
+
test_file = file
|
| 24 |
+
break
|
| 25 |
+
|
| 26 |
+
if not test_file:
|
| 27 |
+
print("❌ No test file found. Please ensure you have a PDF file in the directory.")
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
print(f"📁 Using test file: {test_file}")
|
| 31 |
+
|
| 32 |
+
url = f"{BASE_URL}/hackrx/upload"
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
with open(test_file, 'rb') as f:
|
| 36 |
+
files = {'file': (test_file, f, 'application/pdf')}
|
| 37 |
+
print(f"⏳ Uploading {test_file}...")
|
| 38 |
+
response = requests.post(url, files=files, timeout=60)
|
| 39 |
+
|
| 40 |
+
print(f"Status: {response.status_code}")
|
| 41 |
+
if response.status_code == 200:
|
| 42 |
+
print("✅ Upload successful!")
|
| 43 |
+
result = response.json()
|
| 44 |
+
print(f"Response: {result}")
|
| 45 |
+
return True
|
| 46 |
+
else:
|
| 47 |
+
print(f"❌ Upload failed: {response.text}")
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"❌ Error: {e}")
|
| 52 |
+
return False
|
| 53 |
+
|
| 54 |
+
def test_health_first():
|
| 55 |
+
"""Test health endpoint first"""
|
| 56 |
+
print("🧪 Testing Health Endpoint...")
|
| 57 |
+
try:
|
| 58 |
+
response = requests.get(f"{BASE_URL}/api/health", timeout=10)
|
| 59 |
+
if response.status_code == 200:
|
| 60 |
+
print("✅ Health check passed!")
|
| 61 |
+
return True
|
| 62 |
+
else:
|
| 63 |
+
print(f"❌ Health check failed: {response.text}")
|
| 64 |
+
return False
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"❌ Error: {e}")
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
def main():
|
| 70 |
+
"""Run upload test"""
|
| 71 |
+
print("🚀 Upload Test")
|
| 72 |
+
print("=" * 40)
|
| 73 |
+
|
| 74 |
+
# Test health first
|
| 75 |
+
if not test_health_first():
|
| 76 |
+
print("\n❌ Server not responding. Make sure Flask server is running!")
|
| 77 |
+
print("Run: python app.py")
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
# Test upload
|
| 81 |
+
test_upload()
|
| 82 |
+
|
| 83 |
+
print("\n" + "=" * 40)
|
| 84 |
+
print("📋 POSTMAN UPLOAD GUIDE")
|
| 85 |
+
print("=" * 40)
|
| 86 |
+
print("1. Method: POST")
|
| 87 |
+
print(f"2. URL: {BASE_URL}/hackrx/upload")
|
| 88 |
+
print("3. Body: form-data")
|
| 89 |
+
print("4. Key: file (Type: File)")
|
| 90 |
+
print("5. Value: Select your PDF file")
|
| 91 |
+
print("=" * 40)
|
| 92 |
+
|
| 93 |
+
if __name__ == "__main__":
|
| 94 |
+
main()
|
vector_database.py
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Advanced Vector Database for Document Storage and Retrieval
|
| 3 |
+
Handles document embeddings, similarity search, and metadata management
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import hashlib
|
| 10 |
+
import uuid
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 13 |
+
from dataclasses import dataclass, asdict
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
# Vector database and embedding libraries
|
| 18 |
+
import chromadb
|
| 19 |
+
from chromadb.config import Settings
|
| 20 |
+
from sentence_transformers import SentenceTransformer
|
| 21 |
+
|
| 22 |
+
# Optional LangChain imports with error handling
|
| 23 |
+
try:
|
| 24 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 25 |
+
from langchain_chroma import Chroma
|
| 26 |
+
from langchain.retrievers import ContextualCompressionRetriever
|
| 27 |
+
from langchain.retrievers.document_compressors import LLMChainExtractor
|
| 28 |
+
LANGCHAIN_AVAILABLE = True
|
| 29 |
+
except ImportError as e:
|
| 30 |
+
print(f"⚠️ LangChain components not available: {e}")
|
| 31 |
+
print(" Basic functionality will work without LangChain features")
|
| 32 |
+
LANGCHAIN_AVAILABLE = False
|
| 33 |
+
|
| 34 |
+
# Disable ChromaDB telemetry
|
| 35 |
+
os.environ["ANONYMIZED_TELEMETRY"] = "False"
|
| 36 |
+
|
| 37 |
+
# Configure logging
|
| 38 |
+
logging.basicConfig(level=logging.INFO)
|
| 39 |
+
logger = logging.getLogger(__name__)
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class SearchResult:
|
| 43 |
+
"""Represents a search result with metadata"""
|
| 44 |
+
chunk_id: str
|
| 45 |
+
content: str
|
| 46 |
+
source_file: str
|
| 47 |
+
similarity_score: float
|
| 48 |
+
metadata: Dict[str, Any]
|
| 49 |
+
section_type: Optional[str] = None
|
| 50 |
+
table_data: Optional[Dict[str, Any]] = None
|
| 51 |
+
|
| 52 |
+
class VectorDatabase:
|
| 53 |
+
"""Advanced vector database with GPU optimization and hybrid search"""
|
| 54 |
+
|
| 55 |
+
def __init__(self,
|
| 56 |
+
embedding_model: str = "all-MiniLM-L6-v2",
|
| 57 |
+
collection_name: str = "documents",
|
| 58 |
+
persist_directory: str = "./vector_db",
|
| 59 |
+
use_gpu: bool = True):
|
| 60 |
+
|
| 61 |
+
self.embedding_model = embedding_model
|
| 62 |
+
self.collection_name = collection_name
|
| 63 |
+
self.persist_directory = persist_directory
|
| 64 |
+
self.use_gpu = use_gpu
|
| 65 |
+
|
| 66 |
+
# Initialize embedding model
|
| 67 |
+
self._initialize_embeddings()
|
| 68 |
+
|
| 69 |
+
# Initialize ChromaDB
|
| 70 |
+
self._initialize_chromadb()
|
| 71 |
+
|
| 72 |
+
# Initialize LangChain components
|
| 73 |
+
self._initialize_langchain()
|
| 74 |
+
|
| 75 |
+
logger.info(f"Vector database initialized with model: {embedding_model}")
|
| 76 |
+
|
| 77 |
+
def _initialize_embeddings(self):
|
| 78 |
+
"""Initialize the embedding model"""
|
| 79 |
+
try:
|
| 80 |
+
# Use GPU if available and requested
|
| 81 |
+
device = "cuda" if self.use_gpu and self._check_gpu_availability() else "cpu"
|
| 82 |
+
|
| 83 |
+
self.embedder = SentenceTransformer(self.embedding_model, device=device)
|
| 84 |
+
|
| 85 |
+
# Initialize LangChain embeddings if available
|
| 86 |
+
if LANGCHAIN_AVAILABLE:
|
| 87 |
+
self.langchain_embeddings = HuggingFaceEmbeddings(
|
| 88 |
+
model_name=self.embedding_model,
|
| 89 |
+
model_kwargs={'device': device}
|
| 90 |
+
)
|
| 91 |
+
else:
|
| 92 |
+
self.langchain_embeddings = None
|
| 93 |
+
|
| 94 |
+
logger.info(f"Embedding model loaded on device: {device}")
|
| 95 |
+
|
| 96 |
+
except Exception as e:
|
| 97 |
+
logger.error(f"Error initializing embedding model: {e}")
|
| 98 |
+
raise
|
| 99 |
+
|
| 100 |
+
def _check_gpu_availability(self) -> bool:
|
| 101 |
+
"""Check if GPU is available"""
|
| 102 |
+
try:
|
| 103 |
+
import torch
|
| 104 |
+
return torch.cuda.is_available()
|
| 105 |
+
except ImportError:
|
| 106 |
+
return False
|
| 107 |
+
|
| 108 |
+
def _initialize_chromadb(self):
|
| 109 |
+
"""Initialize ChromaDB client and collection"""
|
| 110 |
+
try:
|
| 111 |
+
# Create persist directory
|
| 112 |
+
os.makedirs(self.persist_directory, exist_ok=True)
|
| 113 |
+
|
| 114 |
+
# Initialize ChromaDB client
|
| 115 |
+
self.chroma_client = chromadb.PersistentClient(
|
| 116 |
+
path=self.persist_directory,
|
| 117 |
+
settings=Settings(
|
| 118 |
+
anonymized_telemetry=False,
|
| 119 |
+
allow_reset=True
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Get or create collection
|
| 124 |
+
self.collection = self.chroma_client.get_or_create_collection(
|
| 125 |
+
name=self.collection_name,
|
| 126 |
+
metadata={"hnsw:space": "cosine"}
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
logger.info(f"ChromaDB collection '{self.collection_name}' initialized")
|
| 130 |
+
|
| 131 |
+
except Exception as e:
|
| 132 |
+
logger.error(f"Error initializing ChromaDB: {e}")
|
| 133 |
+
raise
|
| 134 |
+
|
| 135 |
+
def _initialize_langchain(self):
|
| 136 |
+
"""Initialize LangChain components for advanced retrieval"""
|
| 137 |
+
if not LANGCHAIN_AVAILABLE:
|
| 138 |
+
logger.warning("LangChain components not available - advanced features disabled")
|
| 139 |
+
self.langchain_chroma = None
|
| 140 |
+
self.contextual_retriever = None
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
# Initialize LangChain Chroma
|
| 145 |
+
self.langchain_chroma = Chroma(
|
| 146 |
+
client=self.chroma_client,
|
| 147 |
+
collection_name=self.collection_name,
|
| 148 |
+
embedding_function=self.langchain_embeddings
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Initialize contextual compression retriever
|
| 152 |
+
self.contextual_retriever = ContextualCompressionRetriever(
|
| 153 |
+
base_retriever=self.langchain_chroma.as_retriever(
|
| 154 |
+
search_type="similarity",
|
| 155 |
+
search_kwargs={"k": 10}
|
| 156 |
+
),
|
| 157 |
+
base_compressor=LLMChainExtractor.from_llm(
|
| 158 |
+
llm=None, # Will be set later
|
| 159 |
+
prompt_template="Extract the most relevant information from the following text: {text}"
|
| 160 |
+
)
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
logger.info("LangChain components initialized")
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
logger.error(f"Error initializing LangChain components: {e}")
|
| 167 |
+
# Continue without LangChain components if they fail
|
| 168 |
+
self.langchain_chroma = None
|
| 169 |
+
self.contextual_retriever = None
|
| 170 |
+
|
| 171 |
+
def add_documents(self, chunks: List[Any]) -> bool:
|
| 172 |
+
"""Add document chunks to the vector database"""
|
| 173 |
+
try:
|
| 174 |
+
if not chunks:
|
| 175 |
+
logger.warning("No chunks to add")
|
| 176 |
+
return False
|
| 177 |
+
|
| 178 |
+
# Prepare data for ChromaDB
|
| 179 |
+
ids = []
|
| 180 |
+
texts = []
|
| 181 |
+
metadatas = []
|
| 182 |
+
embeddings = []
|
| 183 |
+
|
| 184 |
+
for chunk in chunks:
|
| 185 |
+
# Generate unique ID
|
| 186 |
+
chunk_id = chunk.chunk_id if hasattr(chunk, 'chunk_id') else str(uuid.uuid4())
|
| 187 |
+
|
| 188 |
+
# Get content
|
| 189 |
+
content = chunk.content if hasattr(chunk, 'content') else str(chunk)
|
| 190 |
+
|
| 191 |
+
# Create metadata (filter out None values)
|
| 192 |
+
metadata = {
|
| 193 |
+
'source_file': getattr(chunk, 'source_file', 'unknown'),
|
| 194 |
+
'file_type': getattr(chunk, 'file_type', 'unknown'),
|
| 195 |
+
'section_type': getattr(chunk, 'section_type', 'text'),
|
| 196 |
+
'chunk_index': getattr(chunk, 'chunk_index', 0),
|
| 197 |
+
'confidence_score': getattr(chunk, 'confidence_score', 1.0),
|
| 198 |
+
'timestamp': datetime.now().isoformat()
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
# Add page_number only if it's not None
|
| 202 |
+
page_number = getattr(chunk, 'page_number', None)
|
| 203 |
+
if page_number is not None:
|
| 204 |
+
metadata['page_number'] = page_number
|
| 205 |
+
|
| 206 |
+
# Add table data if present
|
| 207 |
+
if hasattr(chunk, 'table_data') and chunk.table_data:
|
| 208 |
+
metadata['table_data'] = json.dumps(chunk.table_data)
|
| 209 |
+
|
| 210 |
+
# Generate embedding
|
| 211 |
+
embedding = self.embedder.encode(content, convert_to_tensor=False)
|
| 212 |
+
|
| 213 |
+
ids.append(chunk_id)
|
| 214 |
+
texts.append(content)
|
| 215 |
+
metadatas.append(metadata)
|
| 216 |
+
embeddings.append(embedding.tolist())
|
| 217 |
+
|
| 218 |
+
# Add to ChromaDB
|
| 219 |
+
self.collection.add(
|
| 220 |
+
ids=ids,
|
| 221 |
+
documents=texts,
|
| 222 |
+
metadatas=metadatas,
|
| 223 |
+
embeddings=embeddings
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
logger.info(f"Successfully added {len(chunks)} chunks to vector database")
|
| 227 |
+
return True
|
| 228 |
+
|
| 229 |
+
except Exception as e:
|
| 230 |
+
logger.error(f"Error adding documents to vector database: {e}")
|
| 231 |
+
return False
|
| 232 |
+
|
| 233 |
+
def add_document(self, content: str, metadata: Dict[str, Any]) -> bool:
|
| 234 |
+
"""Add a single document to the vector database"""
|
| 235 |
+
try:
|
| 236 |
+
# Generate unique ID
|
| 237 |
+
chunk_id = str(uuid.uuid4())
|
| 238 |
+
|
| 239 |
+
# Create metadata with defaults
|
| 240 |
+
doc_metadata = {
|
| 241 |
+
'source_file': metadata.get('source_file', 'unknown'),
|
| 242 |
+
'file_type': metadata.get('file_type', 'unknown'),
|
| 243 |
+
'section_type': metadata.get('section_type', 'text'),
|
| 244 |
+
'chunk_index': metadata.get('chunk_index', 0),
|
| 245 |
+
'confidence_score': metadata.get('confidence_score', 1.0),
|
| 246 |
+
'timestamp': datetime.now().isoformat()
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
# Add additional metadata
|
| 250 |
+
for key, value in metadata.items():
|
| 251 |
+
if key not in doc_metadata and value is not None:
|
| 252 |
+
doc_metadata[key] = value
|
| 253 |
+
|
| 254 |
+
# Generate embedding
|
| 255 |
+
embedding = self.embedder.encode(content, convert_to_tensor=False)
|
| 256 |
+
|
| 257 |
+
# Add to ChromaDB
|
| 258 |
+
self.collection.add(
|
| 259 |
+
ids=[chunk_id],
|
| 260 |
+
documents=[content],
|
| 261 |
+
metadatas=[doc_metadata],
|
| 262 |
+
embeddings=[embedding.tolist()]
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
logger.info(f"Successfully added document to vector database")
|
| 266 |
+
return True
|
| 267 |
+
|
| 268 |
+
except Exception as e:
|
| 269 |
+
logger.error(f"Error adding document to vector database: {e}")
|
| 270 |
+
return False
|
| 271 |
+
|
| 272 |
+
def search_documents(self, query: str, n_results: int = 5, similarity_threshold: float = 0.7) -> List[Dict[str, Any]]:
|
| 273 |
+
"""Search for documents and return as dictionary format for compatibility"""
|
| 274 |
+
try:
|
| 275 |
+
search_results = self.search_similar(query, n_results, similarity_threshold)
|
| 276 |
+
|
| 277 |
+
# Convert to dictionary format
|
| 278 |
+
results = []
|
| 279 |
+
for result in search_results:
|
| 280 |
+
results.append({
|
| 281 |
+
'content': result.content,
|
| 282 |
+
'source_file': result.source_file,
|
| 283 |
+
'similarity_score': result.similarity_score,
|
| 284 |
+
'metadata': result.metadata,
|
| 285 |
+
'section_type': result.section_type,
|
| 286 |
+
'table_data': result.table_data
|
| 287 |
+
})
|
| 288 |
+
|
| 289 |
+
return results
|
| 290 |
+
|
| 291 |
+
except Exception as e:
|
| 292 |
+
logger.error(f"Error searching documents: {e}")
|
| 293 |
+
return []
|
| 294 |
+
|
| 295 |
+
def search_similar(self,
|
| 296 |
+
query: str,
|
| 297 |
+
n_results: int = 5,
|
| 298 |
+
similarity_threshold: float = 0.7,
|
| 299 |
+
filter_metadata: Optional[Dict[str, Any]] = None) -> List[SearchResult]:
|
| 300 |
+
"""Search for similar documents using semantic similarity"""
|
| 301 |
+
try:
|
| 302 |
+
# Generate query embedding
|
| 303 |
+
query_embedding = self.embedder.encode(query, convert_to_tensor=False)
|
| 304 |
+
|
| 305 |
+
# Prepare where clause for filtering
|
| 306 |
+
where_clause = None
|
| 307 |
+
if filter_metadata:
|
| 308 |
+
where_clause = filter_metadata
|
| 309 |
+
|
| 310 |
+
# Search in ChromaDB
|
| 311 |
+
results = self.collection.query(
|
| 312 |
+
query_embeddings=[query_embedding.tolist()],
|
| 313 |
+
n_results=n_results,
|
| 314 |
+
where=where_clause,
|
| 315 |
+
include=['documents', 'metadatas', 'distances']
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
# Process results
|
| 319 |
+
search_results = []
|
| 320 |
+
for i in range(len(results['ids'][0])):
|
| 321 |
+
chunk_id = results['ids'][0][i]
|
| 322 |
+
content = results['documents'][0][i]
|
| 323 |
+
metadata = results['metadatas'][0][i]
|
| 324 |
+
distance = results['distances'][0][i]
|
| 325 |
+
|
| 326 |
+
# Convert distance to similarity score
|
| 327 |
+
similarity_score = 1 - distance
|
| 328 |
+
|
| 329 |
+
# Filter by similarity threshold
|
| 330 |
+
if similarity_score >= similarity_threshold:
|
| 331 |
+
# Parse table data if present
|
| 332 |
+
table_data = None
|
| 333 |
+
if 'table_data' in metadata and metadata['table_data']:
|
| 334 |
+
try:
|
| 335 |
+
table_data = json.loads(metadata['table_data'])
|
| 336 |
+
except:
|
| 337 |
+
pass
|
| 338 |
+
|
| 339 |
+
result = SearchResult(
|
| 340 |
+
chunk_id=chunk_id,
|
| 341 |
+
content=content,
|
| 342 |
+
source_file=metadata.get('source_file', 'unknown'),
|
| 343 |
+
similarity_score=similarity_score,
|
| 344 |
+
metadata=metadata,
|
| 345 |
+
section_type=metadata.get('section_type', 'text'),
|
| 346 |
+
table_data=table_data
|
| 347 |
+
)
|
| 348 |
+
search_results.append(result)
|
| 349 |
+
|
| 350 |
+
# Sort by similarity score
|
| 351 |
+
search_results.sort(key=lambda x: x.similarity_score, reverse=True)
|
| 352 |
+
|
| 353 |
+
logger.info(f"Found {len(search_results)} similar documents for query")
|
| 354 |
+
return search_results
|
| 355 |
+
|
| 356 |
+
except Exception as e:
|
| 357 |
+
logger.error(f"Error searching vector database: {e}")
|
| 358 |
+
return []
|
| 359 |
+
|
| 360 |
+
def hybrid_search(self,
|
| 361 |
+
query: str,
|
| 362 |
+
n_results: int = 5,
|
| 363 |
+
semantic_weight: float = 0.7,
|
| 364 |
+
keyword_weight: float = 0.3) -> List[SearchResult]:
|
| 365 |
+
"""Perform hybrid search combining semantic and keyword matching"""
|
| 366 |
+
try:
|
| 367 |
+
# Semantic search
|
| 368 |
+
semantic_results = self.search_similar(query, n_results=n_results*2)
|
| 369 |
+
|
| 370 |
+
# Keyword search (simple implementation)
|
| 371 |
+
keyword_results = self._keyword_search(query, n_results=n_results*2)
|
| 372 |
+
|
| 373 |
+
# Combine and rank results
|
| 374 |
+
combined_results = self._combine_search_results(
|
| 375 |
+
semantic_results,
|
| 376 |
+
keyword_results,
|
| 377 |
+
semantic_weight,
|
| 378 |
+
keyword_weight
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
# Return top results
|
| 382 |
+
return combined_results[:n_results]
|
| 383 |
+
|
| 384 |
+
except Exception as e:
|
| 385 |
+
logger.error(f"Error in hybrid search: {e}")
|
| 386 |
+
return self.search_similar(query, n_results)
|
| 387 |
+
|
| 388 |
+
def _keyword_search(self, query: str, n_results: int = 5) -> List[SearchResult]:
|
| 389 |
+
"""Simple keyword-based search"""
|
| 390 |
+
try:
|
| 391 |
+
# Get all documents
|
| 392 |
+
all_results = self.collection.get()
|
| 393 |
+
|
| 394 |
+
keyword_results = []
|
| 395 |
+
query_terms = query.lower().split()
|
| 396 |
+
|
| 397 |
+
for i, content in enumerate(all_results['documents']):
|
| 398 |
+
content_lower = content.lower()
|
| 399 |
+
|
| 400 |
+
# Calculate keyword match score
|
| 401 |
+
matches = sum(1 for term in query_terms if term in content_lower)
|
| 402 |
+
if matches > 0:
|
| 403 |
+
score = matches / len(query_terms)
|
| 404 |
+
|
| 405 |
+
result = SearchResult(
|
| 406 |
+
chunk_id=all_results['ids'][i],
|
| 407 |
+
content=content,
|
| 408 |
+
source_file=all_results['metadatas'][i].get('source_file', 'unknown'),
|
| 409 |
+
similarity_score=score,
|
| 410 |
+
metadata=all_results['metadatas'][i],
|
| 411 |
+
section_type=all_results['metadatas'][i].get('section_type', 'text')
|
| 412 |
+
)
|
| 413 |
+
keyword_results.append(result)
|
| 414 |
+
|
| 415 |
+
# Sort by score
|
| 416 |
+
keyword_results.sort(key=lambda x: x.similarity_score, reverse=True)
|
| 417 |
+
return keyword_results[:n_results]
|
| 418 |
+
|
| 419 |
+
except Exception as e:
|
| 420 |
+
logger.error(f"Error in keyword search: {e}")
|
| 421 |
+
return []
|
| 422 |
+
|
| 423 |
+
def _combine_search_results(self,
|
| 424 |
+
semantic_results: List[SearchResult],
|
| 425 |
+
keyword_results: List[SearchResult],
|
| 426 |
+
semantic_weight: float,
|
| 427 |
+
keyword_weight: float) -> List[SearchResult]:
|
| 428 |
+
"""Combine semantic and keyword search results"""
|
| 429 |
+
try:
|
| 430 |
+
# Create a dictionary to store combined scores
|
| 431 |
+
combined_scores = {}
|
| 432 |
+
|
| 433 |
+
# Add semantic results
|
| 434 |
+
for result in semantic_results:
|
| 435 |
+
combined_scores[result.chunk_id] = {
|
| 436 |
+
'result': result,
|
| 437 |
+
'semantic_score': result.similarity_score,
|
| 438 |
+
'keyword_score': 0.0
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
# Add keyword results
|
| 442 |
+
for result in keyword_results:
|
| 443 |
+
if result.chunk_id in combined_scores:
|
| 444 |
+
combined_scores[result.chunk_id]['keyword_score'] = result.similarity_score
|
| 445 |
+
else:
|
| 446 |
+
combined_scores[result.chunk_id] = {
|
| 447 |
+
'result': result,
|
| 448 |
+
'semantic_score': 0.0,
|
| 449 |
+
'keyword_score': result.similarity_score
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
# Calculate combined scores
|
| 453 |
+
combined_results = []
|
| 454 |
+
for chunk_id, scores in combined_scores.items():
|
| 455 |
+
combined_score = (scores['semantic_score'] * semantic_weight +
|
| 456 |
+
scores['keyword_score'] * keyword_weight)
|
| 457 |
+
|
| 458 |
+
# Update the result with combined score
|
| 459 |
+
result = scores['result']
|
| 460 |
+
result.similarity_score = combined_score
|
| 461 |
+
combined_results.append(result)
|
| 462 |
+
|
| 463 |
+
# Sort by combined score
|
| 464 |
+
combined_results.sort(key=lambda x: x.similarity_score, reverse=True)
|
| 465 |
+
return combined_results
|
| 466 |
+
|
| 467 |
+
except Exception as e:
|
| 468 |
+
logger.error(f"Error combining search results: {e}")
|
| 469 |
+
return semantic_results
|
| 470 |
+
|
| 471 |
+
def get_document_statistics(self) -> Dict[str, Any]:
|
| 472 |
+
"""Get statistics about the vector database"""
|
| 473 |
+
try:
|
| 474 |
+
# Get collection info
|
| 475 |
+
count = self.collection.count()
|
| 476 |
+
|
| 477 |
+
# Get unique sources
|
| 478 |
+
all_results = self.collection.get()
|
| 479 |
+
sources = set()
|
| 480 |
+
file_types = set()
|
| 481 |
+
section_types = set()
|
| 482 |
+
|
| 483 |
+
for metadata in all_results['metadatas']:
|
| 484 |
+
sources.add(metadata.get('source_file', 'unknown'))
|
| 485 |
+
file_types.add(metadata.get('file_type', 'unknown'))
|
| 486 |
+
section_types.add(metadata.get('section_type', 'text'))
|
| 487 |
+
|
| 488 |
+
stats = {
|
| 489 |
+
'total_chunks': count,
|
| 490 |
+
'unique_sources': len(sources),
|
| 491 |
+
'file_types': list(file_types),
|
| 492 |
+
'section_types': list(section_types),
|
| 493 |
+
'sources': list(sources),
|
| 494 |
+
'embedding_model': self.embedding_model,
|
| 495 |
+
'collection_name': self.collection_name
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
return stats
|
| 499 |
+
|
| 500 |
+
except Exception as e:
|
| 501 |
+
logger.error(f"Error getting document statistics: {e}")
|
| 502 |
+
return {}
|
| 503 |
+
|
| 504 |
+
def delete_documents(self, source_file: str) -> bool:
|
| 505 |
+
"""Delete all documents from a specific source file"""
|
| 506 |
+
try:
|
| 507 |
+
# Get documents from the source
|
| 508 |
+
results = self.collection.get(
|
| 509 |
+
where={"source_file": source_file}
|
| 510 |
+
)
|
| 511 |
+
|
| 512 |
+
if results['ids']:
|
| 513 |
+
# Delete the documents
|
| 514 |
+
self.collection.delete(ids=results['ids'])
|
| 515 |
+
logger.info(f"Deleted {len(results['ids'])} chunks from {source_file}")
|
| 516 |
+
return True
|
| 517 |
+
else:
|
| 518 |
+
logger.warning(f"No documents found for source: {source_file}")
|
| 519 |
+
return False
|
| 520 |
+
|
| 521 |
+
except Exception as e:
|
| 522 |
+
logger.error(f"Error deleting documents: {e}")
|
| 523 |
+
return False
|
| 524 |
+
|
| 525 |
+
def clear_database(self) -> bool:
|
| 526 |
+
"""Clear all documents from the database"""
|
| 527 |
+
try:
|
| 528 |
+
self.collection.delete(where={})
|
| 529 |
+
logger.info("Cleared all documents from vector database")
|
| 530 |
+
return True
|
| 531 |
+
except Exception as e:
|
| 532 |
+
logger.error(f"Error clearing database: {e}")
|
| 533 |
+
return False
|
| 534 |
+
|
| 535 |
+
def export_database(self, export_path: str) -> bool:
|
| 536 |
+
"""Export database statistics and metadata"""
|
| 537 |
+
try:
|
| 538 |
+
stats = self.get_document_statistics()
|
| 539 |
+
|
| 540 |
+
with open(export_path, 'w') as f:
|
| 541 |
+
json.dump(stats, f, indent=2)
|
| 542 |
+
|
| 543 |
+
logger.info(f"Database exported to: {export_path}")
|
| 544 |
+
return True
|
| 545 |
+
|
| 546 |
+
except Exception as e:
|
| 547 |
+
logger.error(f"Error exporting database: {e}")
|
| 548 |
+
return False
|
| 549 |
+
|
| 550 |
+
# Example usage
|
| 551 |
+
if __name__ == "__main__":
|
| 552 |
+
# Initialize vector database
|
| 553 |
+
vector_db = VectorDatabase(use_gpu=True)
|
| 554 |
+
|
| 555 |
+
# Test search
|
| 556 |
+
results = vector_db.search_similar("insurance policy coverage", n_results=3)
|
| 557 |
+
|
| 558 |
+
print(f"Found {len(results)} results:")
|
| 559 |
+
for i, result in enumerate(results):
|
| 560 |
+
print(f"\nResult {i+1}:")
|
| 561 |
+
print(f"Score: {result.similarity_score:.3f}")
|
| 562 |
+
print(f"Source: {result.source_file}")
|
| 563 |
+
print(f"Content: {result.content[:100]}...")
|
| 564 |
+
|
| 565 |
+
# Get statistics
|
| 566 |
+
stats = vector_db.get_document_statistics()
|
| 567 |
+
print(f"\nDatabase statistics: {stats}")
|