moazx commited on
Commit
2a8faae
·
0 Parent(s):

Initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +15 -0
  2. .env +10 -0
  3. .env.example +10 -0
  4. Dockerfile +50 -0
  5. api/__init__.py +0 -0
  6. api/__pycache__/__init__.cpython-311.pyc +0 -0
  7. api/__pycache__/__init__.cpython-312.pyc +0 -0
  8. api/__pycache__/__init__.cpython-313.pyc +0 -0
  9. api/__pycache__/app.cpython-311.pyc +0 -0
  10. api/__pycache__/app.cpython-312.pyc +0 -0
  11. api/__pycache__/app.cpython-313.pyc +0 -0
  12. api/__pycache__/exceptions.cpython-311.pyc +0 -0
  13. api/__pycache__/exceptions.cpython-313.pyc +0 -0
  14. api/__pycache__/middleware.cpython-311.pyc +0 -0
  15. api/__pycache__/middleware.cpython-313.pyc +0 -0
  16. api/__pycache__/models.cpython-311.pyc +0 -0
  17. api/__pycache__/models.cpython-313.pyc +0 -0
  18. api/app.py +105 -0
  19. api/exceptions.py +71 -0
  20. api/middleware.py +100 -0
  21. api/models.py +97 -0
  22. api/routers/__init__.py +0 -0
  23. api/routers/__pycache__/__init__.cpython-311.pyc +0 -0
  24. api/routers/__pycache__/__init__.cpython-313.pyc +0 -0
  25. api/routers/__pycache__/health.cpython-311.pyc +0 -0
  26. api/routers/__pycache__/health.cpython-313.pyc +0 -0
  27. api/routers/__pycache__/medical.cpython-311.pyc +0 -0
  28. api/routers/__pycache__/medical.cpython-313.pyc +0 -0
  29. api/routers/health.py +136 -0
  30. api/routers/medical.py +58 -0
  31. api/routers/side_effects.py +165 -0
  32. core/__init__.py +0 -0
  33. core/__pycache__/__init__.cpython-311.pyc +0 -0
  34. core/__pycache__/__init__.cpython-313.pyc +0 -0
  35. core/__pycache__/agent.cpython-311.pyc +0 -0
  36. core/__pycache__/agent.cpython-313.pyc +0 -0
  37. core/__pycache__/background_init.cpython-311.pyc +0 -0
  38. core/__pycache__/background_init.cpython-313.pyc +0 -0
  39. core/__pycache__/config.cpython-311.pyc +0 -0
  40. core/__pycache__/config.cpython-313.pyc +0 -0
  41. core/__pycache__/data_loaders.cpython-311.pyc +0 -0
  42. core/__pycache__/data_loaders.cpython-313.pyc +0 -0
  43. core/__pycache__/github_storage.cpython-311.pyc +0 -0
  44. core/__pycache__/github_storage.cpython-313.pyc +0 -0
  45. core/__pycache__/retrievers.cpython-311.pyc +0 -0
  46. core/__pycache__/retrievers.cpython-313.pyc +0 -0
  47. core/__pycache__/text_processors.cpython-311.pyc +0 -0
  48. core/__pycache__/text_processors.cpython-313.pyc +0 -0
  49. core/__pycache__/tools.cpython-311.pyc +0 -0
  50. core/__pycache__/tools.cpython-313.pyc +0 -0
.dockerignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.sqlite3
6
+ .env
7
+ .env.*
8
+ .git/
9
+ .gitignore
10
+ .vscode/
11
+ .idea/
12
+ logs/
13
+ data/
14
+ **/__pycache__/
15
+ **/*.py[cod]
.env ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenAI Configuration
2
+ OPENAI_API_KEY=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
3
+
4
+ LANGSMITH_API_KEY=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
5
+
6
+ LANGSMITH_PROJECT=medical_chatbot
7
+ LANGCHAIN_PROJECT=medical_chatbot
8
+
9
+ LANGSMITH_URL=https://api.smith.langchain.com
10
+
.env.example ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenAI Configuration
2
+ OPENAI_API_KEY=
3
+ OPENAI_BASE_URL=
4
+
5
+ LANGSMITH_API_KEY=
6
+
7
+ LANGSMITH_PROJECT=
8
+ LANGCHAIN_PROJECT=
9
+
10
+ LANGSMITH_URL=
Dockerfile ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========================
2
+ # Stage 1 - Builder
3
+ # ========================
4
+ FROM python:3.10-slim AS builder
5
+
6
+ # Install build dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ build-essential \
9
+ gcc \
10
+ g++ \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ WORKDIR /app
14
+
15
+ # Copy only requirements first (better caching)
16
+ COPY requirements.txt .
17
+
18
+ # Install dependencies
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # ========================
22
+ # Stage 2 - Final Runtime Image
23
+ # ========================
24
+ FROM python:3.10-slim
25
+
26
+ # Install minimal runtime dependencies
27
+ RUN apt-get update && apt-get install -y \
28
+ && rm -rf /var/lib/apt/lists/*
29
+
30
+ # Create a non-root user
31
+ RUN useradd --create-home --shell /bin/bash appuser
32
+ WORKDIR /app
33
+
34
+ # Copy installed packages and application code
35
+ COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
36
+ COPY --from=builder /usr/local/bin /usr/local/bin
37
+ COPY . /app
38
+
39
+ # Set permissions
40
+ RUN chown -R appuser:appuser /app
41
+ USER appuser
42
+
43
+ # Expose port
44
+ EXPOSE 8000
45
+
46
+ # Set the working directory to the backend folder
47
+ WORKDIR /app
48
+
49
+ # Command to run the FastAPI app
50
+ CMD ["sh", "-c", "uvicorn api.app:app --host 0.0.0.0 --port ${PORT:-8000}"]
api/__init__.py ADDED
File without changes
api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (144 Bytes). View file
 
api/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (132 Bytes). View file
 
api/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (132 Bytes). View file
 
api/__pycache__/app.cpython-311.pyc ADDED
Binary file (4.32 kB). View file
 
api/__pycache__/app.cpython-312.pyc ADDED
Binary file (3.76 kB). View file
 
api/__pycache__/app.cpython-313.pyc ADDED
Binary file (3.74 kB). View file
 
api/__pycache__/exceptions.cpython-311.pyc ADDED
Binary file (3.83 kB). View file
 
api/__pycache__/exceptions.cpython-313.pyc ADDED
Binary file (3.43 kB). View file
 
api/__pycache__/middleware.cpython-311.pyc ADDED
Binary file (5.82 kB). View file
 
api/__pycache__/middleware.cpython-313.pyc ADDED
Binary file (5.28 kB). View file
 
api/__pycache__/models.cpython-311.pyc ADDED
Binary file (9.21 kB). View file
 
api/__pycache__/models.cpython-313.pyc ADDED
Binary file (7.53 kB). View file
 
api/app.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import logging
3
+ from contextlib import asynccontextmanager
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.exceptions import RequestValidationError
6
+ from starlette.exceptions import HTTPException as StarletteHTTPException
7
+
8
+ # Import routers
9
+ from api.routers import medical, health
10
+ from api.middleware import (
11
+ ProcessTimeMiddleware,
12
+ LoggingMiddleware,
13
+ RateLimitMiddleware,
14
+ get_cors_middleware_config
15
+ )
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from api.exceptions import (
18
+ http_exception_handler,
19
+ validation_exception_handler,
20
+ general_exception_handler,
21
+ starlette_exception_handler
22
+ )
23
+
24
+ # Configure logging
25
+ logging.basicConfig(level=logging.INFO)
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @asynccontextmanager
30
+ async def lifespan(app: FastAPI):
31
+ """Application lifespan management with background initialization"""
32
+ # Startup
33
+ logger.info("Starting Medical RAG AI Advisor API...")
34
+
35
+ # Start background initialization of heavy components
36
+ try:
37
+ from background_init import start_background_initialization
38
+ logger.info("🚀 Starting background initialization of components...")
39
+ start_background_initialization()
40
+ logger.info("API started successfully (components loading in background)")
41
+ except Exception as e:
42
+ logger.error(f"Failed to start background initialization: {e}")
43
+ logger.info("API started with lazy loading fallback")
44
+
45
+ yield
46
+
47
+ # Shutdown
48
+ logger.info("Shutting down Medical RAG AI Advisor API...")
49
+
50
+
51
+ # Create FastAPI application
52
+ app = FastAPI(
53
+ title="Medical RAG AI Advisor API",
54
+ description="Professional API for medical information retrieval and advisory services",
55
+ version="1.0.0",
56
+ docs_url="/docs",
57
+ redoc_url="/redoc",
58
+ lifespan=lifespan
59
+ )
60
+
61
+ # Add middleware
62
+ app.add_middleware(CORSMiddleware, **get_cors_middleware_config())
63
+ app.add_middleware(ProcessTimeMiddleware)
64
+ app.add_middleware(LoggingMiddleware)
65
+ app.add_middleware(RateLimitMiddleware, calls_per_minute=100) # Adjust as needed
66
+
67
+ # Add exception handlers
68
+ app.add_exception_handler(HTTPException, http_exception_handler)
69
+ app.add_exception_handler(RequestValidationError, validation_exception_handler)
70
+ app.add_exception_handler(StarletteHTTPException, starlette_exception_handler)
71
+ app.add_exception_handler(Exception, general_exception_handler)
72
+
73
+ # Include routers
74
+ app.include_router(health.router)
75
+ app.include_router(medical.router)
76
+
77
+ # Root endpoint
78
+ @app.get("/")
79
+ async def root():
80
+ """Root endpoint with API information"""
81
+ return {
82
+ "name": "Medical RAG AI Advisor API",
83
+ "version": "1.0.0",
84
+ "description": "Professional API for medical information retrieval and advisory services",
85
+ "docs": "/docs",
86
+ "health": "/health",
87
+ "endpoints": {
88
+ "ask": "/ask",
89
+ "ask_stream": "/ask/stream",
90
+ "initialization_status": "/health/initialization"
91
+ }
92
+ }
93
+
94
+
95
+
96
+
97
+ if __name__ == "__main__":
98
+ import uvicorn
99
+ uvicorn.run(
100
+ "api.app:app",
101
+ host="127.0.0.1",
102
+ port=8000,
103
+ reload=True,
104
+ log_level="info"
105
+ )
api/exceptions.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exception handlers for Medical RAG AI Advisor API
3
+ """
4
+ import logging
5
+ from datetime import datetime
6
+ from fastapi import Request, HTTPException
7
+ from fastapi.responses import JSONResponse
8
+ from fastapi.exceptions import RequestValidationError
9
+ from starlette.exceptions import HTTPException as StarletteHTTPException
10
+
11
+ from api.models import ErrorResponse
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ async def http_exception_handler(request: Request, exc: HTTPException):
17
+ """Handle HTTP exceptions"""
18
+ logger.error(f"HTTP Exception: {exc.status_code} - {exc.detail}")
19
+
20
+ return JSONResponse(
21
+ status_code=exc.status_code,
22
+ content=ErrorResponse(
23
+ error="HTTP_ERROR",
24
+ message=exc.detail,
25
+ timestamp=datetime.now().isoformat()
26
+ ).dict()
27
+ )
28
+
29
+
30
+ async def validation_exception_handler(request: Request, exc: RequestValidationError):
31
+ """Handle request validation errors"""
32
+ logger.error(f"Validation Error: {exc.errors()}")
33
+
34
+ return JSONResponse(
35
+ status_code=422,
36
+ content=ErrorResponse(
37
+ error="VALIDATION_ERROR",
38
+ message="Request validation failed",
39
+ details={"validation_errors": exc.errors()},
40
+ timestamp=datetime.now().isoformat()
41
+ ).dict()
42
+ )
43
+
44
+
45
+ async def general_exception_handler(request: Request, exc: Exception):
46
+ """Handle general exceptions"""
47
+ logger.error(f"Unhandled Exception: {type(exc).__name__} - {str(exc)}")
48
+
49
+ return JSONResponse(
50
+ status_code=500,
51
+ content=ErrorResponse(
52
+ error="INTERNAL_SERVER_ERROR",
53
+ message="An internal server error occurred",
54
+ details={"exception_type": type(exc).__name__},
55
+ timestamp=datetime.now().isoformat()
56
+ ).dict()
57
+ )
58
+
59
+
60
+ async def starlette_exception_handler(request: Request, exc: StarletteHTTPException):
61
+ """Handle Starlette HTTP exceptions"""
62
+ logger.error(f"Starlette HTTP Exception: {exc.status_code} - {exc.detail}")
63
+
64
+ return JSONResponse(
65
+ status_code=exc.status_code,
66
+ content=ErrorResponse(
67
+ error="HTTP_ERROR",
68
+ message=exc.detail,
69
+ timestamp=datetime.now().isoformat()
70
+ ).dict()
71
+ )
api/middleware.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Middleware for Medical RAG AI Advisor API
3
+ """
4
+ import time
5
+ import logging
6
+ from typing import Callable, Awaitable
7
+ from fastapi import Request, Response, HTTPException
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from starlette.middleware.base import BaseHTTPMiddleware
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ProcessTimeMiddleware(BaseHTTPMiddleware):
15
+ """Middleware to add processing time to response headers"""
16
+
17
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
18
+ start_time = time.time()
19
+ response = await call_next(request)
20
+ process_time = time.time() - start_time
21
+ response.headers["X-Process-Time"] = f"{process_time:.4f}"
22
+ return response
23
+
24
+
25
+ class LoggingMiddleware(BaseHTTPMiddleware):
26
+ """Middleware for request/response logging"""
27
+
28
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
29
+ start_time = time.time()
30
+
31
+ # Log request
32
+ logger.info(f"Request: {request.method} {request.url}")
33
+
34
+ try:
35
+ response = await call_next(request)
36
+ process_time = time.time() - start_time
37
+
38
+ # Log response
39
+ logger.info(
40
+ f"Response: {response.status_code} - "
41
+ f"Time: {process_time:.4f}s - "
42
+ f"Path: {request.url.path}"
43
+ )
44
+
45
+ return response
46
+
47
+ except Exception as e:
48
+ process_time = time.time() - start_time
49
+ logger.error(
50
+ f"Error: {str(e)} - "
51
+ f"Time: {process_time:.4f}s - "
52
+ f"Path: {request.url.path}"
53
+ )
54
+ raise
55
+
56
+
57
+ class RateLimitMiddleware(BaseHTTPMiddleware):
58
+ """Simple rate limiting middleware"""
59
+
60
+ def __init__(self, app, calls_per_minute: int = 60):
61
+ super().__init__(app)
62
+ self.calls_per_minute = calls_per_minute
63
+ self.client_calls = {}
64
+
65
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
66
+ client_ip = request.client.host
67
+ current_time = time.time()
68
+
69
+ # Clean old entries
70
+ self.client_calls = {
71
+ ip: calls for ip, calls in self.client_calls.items()
72
+ if any(call_time > current_time - 60 for call_time in calls)
73
+ }
74
+
75
+ # Check rate limit
76
+ if client_ip in self.client_calls:
77
+ recent_calls = [
78
+ call_time for call_time in self.client_calls[client_ip]
79
+ if call_time > current_time - 60
80
+ ]
81
+ if len(recent_calls) >= self.calls_per_minute:
82
+ raise HTTPException(
83
+ status_code=429,
84
+ detail="Rate limit exceeded. Please try again later."
85
+ )
86
+ self.client_calls[client_ip] = recent_calls + [current_time]
87
+ else:
88
+ self.client_calls[client_ip] = [current_time]
89
+
90
+ return await call_next(request)
91
+
92
+
93
+ def get_cors_middleware_config():
94
+ """Get CORS middleware configuration"""
95
+ return {
96
+ "allow_origins": ["*"], # Configure appropriately for production
97
+ "allow_credentials": True,
98
+ "allow_methods": ["*"],
99
+ "allow_headers": ["*"],
100
+ }
api/models.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API Models and Schemas for Medical RAG AI Advisor
3
+ """
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional, List, Dict, Any
6
+ from enum import Enum
7
+
8
+
9
+ class QueryType(str, Enum):
10
+ """Types of medical queries supported"""
11
+ GENERAL = "general"
12
+ DRUG_INTERACTION = "drug_interaction"
13
+ SIDE_EFFECTS = "side_effects"
14
+ GUIDELINES = "guidelines"
15
+ COMPARISON = "comparison"
16
+
17
+
18
+ class QueryRequest(BaseModel):
19
+ """Request model for medical queries"""
20
+ query: str = Field(..., description="Medical question or query", min_length=1)
21
+ query_type: Optional[QueryType] = Field(None, description="Type of medical query")
22
+ context: Optional[str] = Field(None, description="Additional context for the query")
23
+ patient_info: Optional[Dict[str, Any]] = Field(None, description="Patient information if relevant")
24
+
25
+
26
+ class QueryResponse(BaseModel):
27
+ """Response model for medical queries"""
28
+ response: str = Field(..., description="AI-generated medical response")
29
+ sources: Optional[List[str]] = Field(None, description="Sources used for the response")
30
+ confidence: Optional[float] = Field(None, description="Confidence score of the response")
31
+ query_type: Optional[QueryType] = Field(None, description="Detected or specified query type")
32
+ processing_time: Optional[float] = Field(None, description="Time taken to process the query")
33
+
34
+
35
+ class StreamChunk(BaseModel):
36
+ """Model for streaming response chunks"""
37
+ chunk: str = Field(..., description="Chunk of the streaming response")
38
+ is_final: bool = Field(False, description="Whether this is the final chunk")
39
+
40
+
41
+ class SideEffectReport(BaseModel):
42
+ """Model for side effect reporting"""
43
+ drug_name: str = Field(..., description="Name of the drug")
44
+ side_effects: str = Field(..., description="Reported side effects")
45
+ patient_age: Optional[int] = Field(None, description="Patient age")
46
+ patient_gender: Optional[str] = Field(None, description="Patient gender")
47
+ dosage: Optional[str] = Field(None, description="Drug dosage")
48
+ duration: Optional[str] = Field(None, description="Duration of treatment")
49
+ severity: Optional[str] = Field(None, description="Severity of side effects")
50
+ outcome: Optional[str] = Field(None, description="Outcome of the side effects")
51
+ additional_details: Optional[str] = Field(None, description="Additional clinical details")
52
+
53
+
54
+ class SideEffectResponse(BaseModel):
55
+ """Response model for side effect reporting"""
56
+ report_id: str = Field(..., description="Unique identifier for the report")
57
+ status: str = Field(..., description="Status of the report submission")
58
+ message: str = Field(..., description="Confirmation message")
59
+ recommendations: Optional[List[str]] = Field(None, description="Clinical recommendations")
60
+
61
+
62
+ class ComparisonRequest(BaseModel):
63
+ """Request model for provider/treatment comparisons"""
64
+ providers: List[str] = Field(..., description="List of providers to compare", min_items=2)
65
+ criteria: Optional[List[str]] = Field(None, description="Specific criteria for comparison")
66
+
67
+
68
+ class ComparisonResponse(BaseModel):
69
+ """Response model for provider/treatment comparisons"""
70
+ comparison: str = Field(..., description="Detailed comparison analysis")
71
+ summary: Dict[str, Any] = Field(..., description="Summary of key differences")
72
+ recommendations: Optional[str] = Field(None, description="Recommendations based on comparison")
73
+
74
+
75
+ class InitializationStatus(BaseModel):
76
+ """Initialization status response model"""
77
+ is_complete: bool = Field(..., description="Whether initialization is complete")
78
+ status_message: str = Field(..., description="Current initialization status")
79
+ is_successful: bool = Field(..., description="Whether initialization was successful")
80
+ error: Optional[str] = Field(None, description="Initialization error if any")
81
+
82
+
83
+ class HealthStatus(BaseModel):
84
+ """Health check response model"""
85
+ status: str = Field(..., description="API health status")
86
+ version: str = Field(..., description="API version")
87
+ timestamp: str = Field(..., description="Current timestamp")
88
+ components: Dict[str, str] = Field(..., description="Status of system components")
89
+ initialization: Optional[InitializationStatus] = Field(None, description="Background initialization status")
90
+
91
+
92
+ class ErrorResponse(BaseModel):
93
+ """Error response model"""
94
+ error: str = Field(..., description="Error type")
95
+ message: str = Field(..., description="Error message")
96
+ details: Optional[Dict[str, Any]] = Field(None, description="Additional error details")
97
+ timestamp: str = Field(..., description="Error timestamp")
api/routers/__init__.py ADDED
File without changes
api/routers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (182 Bytes). View file
 
api/routers/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (170 Bytes). View file
 
api/routers/__pycache__/health.cpython-311.pyc ADDED
Binary file (5.52 kB). View file
 
api/routers/__pycache__/health.cpython-313.pyc ADDED
Binary file (4.73 kB). View file
 
api/routers/__pycache__/medical.cpython-311.pyc ADDED
Binary file (2.93 kB). View file
 
api/routers/__pycache__/medical.cpython-313.pyc ADDED
Binary file (2.6 kB). View file
 
api/routers/health.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Health Check and System Status Router
3
+ """
4
+ from datetime import datetime
5
+ from fastapi import APIRouter
6
+ import sys
7
+ import os
8
+
9
+ # Add src to path for imports
10
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
11
+
12
+ from api.models import HealthStatus, InitializationStatus
13
+
14
+ router = APIRouter(prefix="/health", tags=["health"])
15
+
16
+
17
+ @router.get("/", response_model=HealthStatus)
18
+ async def health_check():
19
+ """
20
+ Check the health status of the API and its components
21
+ """
22
+ components = {}
23
+
24
+ # Check agent availability
25
+ try:
26
+ from agent import safe_run_agent
27
+ components["agent"] = "healthy"
28
+ except Exception:
29
+ components["agent"] = "unhealthy"
30
+
31
+ # Check vector store
32
+ try:
33
+ from vector_store import VectorStore
34
+ components["vector_store"] = "healthy"
35
+ except Exception:
36
+ components["vector_store"] = "unhealthy"
37
+
38
+ # Check data loaders
39
+ try:
40
+ from data_loaders import load_pdf_documents
41
+ components["data_loaders"] = "healthy"
42
+ except Exception:
43
+ components["data_loaders"] = "unhealthy"
44
+
45
+ # Check tools
46
+ try:
47
+ from tools import medical_guidelines_knowledge_tool
48
+ components["tools"] = "healthy"
49
+ except Exception:
50
+ components["tools"] = "unhealthy"
51
+
52
+ # Check initialization status
53
+ initialization_status = None
54
+ try:
55
+ from background_init import (
56
+ is_initialization_complete,
57
+ get_initialization_status,
58
+ is_initialization_successful,
59
+ get_initialization_error
60
+ )
61
+
62
+ initialization_status = InitializationStatus(
63
+ is_complete=is_initialization_complete(),
64
+ status_message=get_initialization_status(),
65
+ is_successful=is_initialization_successful(),
66
+ error=str(get_initialization_error()) if get_initialization_error() else None
67
+ )
68
+ except Exception as e:
69
+ initialization_status = InitializationStatus(
70
+ is_complete=False,
71
+ status_message=f"Unable to check initialization status: {str(e)}",
72
+ is_successful=False,
73
+ error=str(e)
74
+ )
75
+
76
+ # Overall status
77
+ overall_status = "healthy" if all(
78
+ status == "healthy" for status in components.values()
79
+ ) else "degraded"
80
+
81
+ return HealthStatus(
82
+ status=overall_status,
83
+ version="1.0.0",
84
+ timestamp=datetime.now().isoformat(),
85
+ components=components,
86
+ initialization=initialization_status
87
+ )
88
+
89
+
90
+ @router.get("/ping")
91
+ async def ping():
92
+ """
93
+ Simple ping endpoint for basic connectivity check
94
+ """
95
+ return {"message": "pong", "timestamp": datetime.now().isoformat()}
96
+
97
+
98
+ @router.get("/initialization", response_model=InitializationStatus)
99
+ async def get_initialization_status():
100
+ """
101
+ Get the current initialization status of background components
102
+ """
103
+ try:
104
+ from background_init import (
105
+ is_initialization_complete,
106
+ get_initialization_status,
107
+ is_initialization_successful,
108
+ get_initialization_error
109
+ )
110
+
111
+ return InitializationStatus(
112
+ is_complete=is_initialization_complete(),
113
+ status_message=get_initialization_status(),
114
+ is_successful=is_initialization_successful(),
115
+ error=str(get_initialization_error()) if get_initialization_error() else None
116
+ )
117
+ except Exception as e:
118
+ return InitializationStatus(
119
+ is_complete=False,
120
+ status_message=f"Unable to check initialization status: {str(e)}",
121
+ is_successful=False,
122
+ error=str(e)
123
+ )
124
+
125
+
126
+ @router.get("/version")
127
+ async def get_version():
128
+ """
129
+ Get API version information
130
+ """
131
+ return {
132
+ "version": "1.0.0",
133
+ "name": "Medical RAG AI Advisor API",
134
+ "description": "Professional API for medical information retrieval and advisory services",
135
+ "build_date": "2024-01-01"
136
+ }
api/routers/medical.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Medical Query Router for RAG AI Advisor
3
+ """
4
+ import asyncio
5
+ from fastapi import APIRouter, HTTPException
6
+ from fastapi.responses import StreamingResponse
7
+ import sys
8
+ import os
9
+
10
+ # Add src to path for imports
11
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
12
+
13
+ from core.agent import safe_run_agent, safe_run_agent_streaming
14
+
15
+ router = APIRouter(tags=["medical"])
16
+
17
+
18
+ @router.get("/ask")
19
+ async def ask(query: str):
20
+ """
21
+ Process a medical query - agent decides which tools to use
22
+ """
23
+ try:
24
+ response = await safe_run_agent(user_input=query)
25
+ return {"response": response}
26
+
27
+ except Exception as e:
28
+ raise HTTPException(
29
+ status_code=500,
30
+ detail=f"Error processing medical query: {str(e)}"
31
+ )
32
+
33
+
34
+ @router.get("/ask/stream")
35
+ async def ask_stream(query: str):
36
+ """
37
+ Process a medical query with streaming response - agent decides which tools to use
38
+ """
39
+ async def event_stream():
40
+ try:
41
+ chunk_buffer = ""
42
+ async for chunk in safe_run_agent_streaming(user_input=query):
43
+ chunk_buffer += chunk
44
+
45
+ # Send chunks in reasonable sizes for smoother streaming
46
+ if len(chunk_buffer) >= 10: # Adjust this value as needed
47
+ yield chunk_buffer
48
+ chunk_buffer = ""
49
+ await asyncio.sleep(0.01) # Small delay for smoother streaming
50
+
51
+ # Send any remaining content
52
+ if chunk_buffer:
53
+ yield chunk_buffer
54
+
55
+ except Exception as e:
56
+ yield f"Error: {str(e)}"
57
+
58
+ return StreamingResponse(event_stream(), media_type="text/markdown")
api/routers/side_effects.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Side Effects Reporting Router
3
+ """
4
+ import uuid
5
+ import time
6
+ from datetime import datetime
7
+ from fastapi import APIRouter, HTTPException
8
+ import sys
9
+ import os
10
+
11
+ # Add backend and src directories to path for imports
12
+ backend_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
13
+ src_path = os.path.join(backend_path, 'src')
14
+ sys.path.append(backend_path)
15
+ sys.path.append(src_path)
16
+
17
+ from api.models import SideEffectReport
18
+ from tools import side_effect_recording_tool
19
+ from github_storage import get_github_storage
20
+
21
+ router = APIRouter(prefix="/side-effects", tags=["side-effects"])
22
+
23
+
24
+ @router.post("/report")
25
+ async def report_side_effect(report: SideEffectReport):
26
+ """
27
+ Submit a side effect report for pharmacovigilance
28
+ """
29
+ try:
30
+ # Prepare the report text for the side effect tool
31
+ report_text = f"Drug: {report.drug_name}, Side effects: {report.side_effects}"
32
+
33
+ if report.patient_age:
34
+ report_text += f", Patient age: {report.patient_age}"
35
+ if report.patient_gender:
36
+ report_text += f", Patient gender: {report.patient_gender}"
37
+ if report.dosage:
38
+ report_text += f", Dosage: {report.dosage}"
39
+ if report.duration:
40
+ report_text += f", Duration: {report.duration}"
41
+ if report.severity:
42
+ report_text += f", Severity: {report.severity}"
43
+ if report.outcome:
44
+ report_text += f", Outcome: {report.outcome}"
45
+ if report.additional_details:
46
+ report_text += f", Additional details: {report.additional_details}"
47
+
48
+ # Process through the existing side effect recording tool
49
+ result = side_effect_recording_tool(report_text)
50
+
51
+ return {"result": result}
52
+
53
+ except Exception as e:
54
+ raise HTTPException(
55
+ status_code=500,
56
+ detail=f"Error processing side effect report: {str(e)}"
57
+ )
58
+
59
+
60
+ @router.get("/reports/summary")
61
+ async def get_reports_summary():
62
+ """
63
+ Get summary of side effect reports from GitHub repository
64
+ """
65
+ try:
66
+ import pandas as pd
67
+ import os
68
+
69
+ # Try to get reports from GitHub first
70
+ github_storage = get_github_storage()
71
+ reports = github_storage.get_side_effects_reports()
72
+
73
+ if not reports:
74
+ # Fallback to local file if GitHub fails
75
+ csv_path = "side_effects_reports.csv"
76
+
77
+ if not os.path.exists(csv_path):
78
+ return {
79
+ "total_reports": 0,
80
+ "message": "No side effect reports found"
81
+ }
82
+
83
+ df = pd.read_csv(csv_path)
84
+ else:
85
+ # Convert GitHub reports to DataFrame
86
+ df = pd.DataFrame(reports)
87
+
88
+ if df.empty:
89
+ return {
90
+ "total_reports": 0,
91
+ "message": "No side effect reports found"
92
+ }
93
+
94
+ summary = {
95
+ "total_reports": len(df),
96
+ "unique_drugs": df['drug_name'].nunique() if 'drug_name' in df.columns else 0,
97
+ "recent_reports": len(df[df['timestamp'] >= (datetime.now() - pd.Timedelta(days=30)).strftime('%Y-%m-%d')]) if 'timestamp' in df.columns else 0,
98
+ "most_reported_drugs": df['drug_name'].value_counts().head(5).to_dict() if 'drug_name' in df.columns else {}
99
+ }
100
+
101
+ return summary
102
+
103
+ except Exception as e:
104
+ raise HTTPException(
105
+ status_code=500,
106
+ detail=f"Error retrieving reports summary: {str(e)}"
107
+ )
108
+
109
+
110
+ @router.get("/drug/{drug_name}/reports")
111
+ async def get_drug_reports(drug_name: str):
112
+ """
113
+ Get side effect reports for a specific drug from GitHub repository
114
+ """
115
+ try:
116
+ # Try to get reports from GitHub first
117
+ github_storage = get_github_storage()
118
+ reports = github_storage.get_drug_reports(drug_name)
119
+
120
+ if reports:
121
+ return {
122
+ "drug_name": drug_name,
123
+ "total_reports": len(reports),
124
+ "reports": reports
125
+ }
126
+
127
+ # Fallback to local file if GitHub fails or no reports found
128
+ import pandas as pd
129
+ import os
130
+
131
+ csv_path = "side_effects_reports.csv"
132
+
133
+ if not os.path.exists(csv_path):
134
+ return {
135
+ "drug_name": drug_name,
136
+ "reports": [],
137
+ "message": "No reports found"
138
+ }
139
+
140
+ df = pd.read_csv(csv_path)
141
+
142
+ # Filter reports for the specific drug (case-insensitive)
143
+ drug_reports = df[df['drug_name'].str.lower() == drug_name.lower()]
144
+
145
+ if drug_reports.empty:
146
+ return {
147
+ "drug_name": drug_name,
148
+ "reports": [],
149
+ "message": f"No reports found for {drug_name}"
150
+ }
151
+
152
+ # Convert to list of dictionaries
153
+ local_reports = drug_reports.to_dict('records')
154
+
155
+ return {
156
+ "drug_name": drug_name,
157
+ "total_reports": len(local_reports),
158
+ "reports": local_reports
159
+ }
160
+
161
+ except Exception as e:
162
+ raise HTTPException(
163
+ status_code=500,
164
+ detail=f"Error retrieving drug reports: {str(e)}"
165
+ )
core/__init__.py ADDED
File without changes
core/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (145 Bytes). View file
 
core/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (133 Bytes). View file
 
core/__pycache__/agent.cpython-311.pyc ADDED
Binary file (36.1 kB). View file
 
core/__pycache__/agent.cpython-313.pyc ADDED
Binary file (32.5 kB). View file
 
core/__pycache__/background_init.cpython-311.pyc ADDED
Binary file (8.47 kB). View file
 
core/__pycache__/background_init.cpython-313.pyc ADDED
Binary file (7.77 kB). View file
 
core/__pycache__/config.cpython-311.pyc ADDED
Binary file (6.3 kB). View file
 
core/__pycache__/config.cpython-313.pyc ADDED
Binary file (6.24 kB). View file
 
core/__pycache__/data_loaders.cpython-311.pyc ADDED
Binary file (4.93 kB). View file
 
core/__pycache__/data_loaders.cpython-313.pyc ADDED
Binary file (4.34 kB). View file
 
core/__pycache__/github_storage.cpython-311.pyc ADDED
Binary file (17.7 kB). View file
 
core/__pycache__/github_storage.cpython-313.pyc ADDED
Binary file (15.7 kB). View file
 
core/__pycache__/retrievers.cpython-311.pyc ADDED
Binary file (8.81 kB). View file
 
core/__pycache__/retrievers.cpython-313.pyc ADDED
Binary file (8 kB). View file
 
core/__pycache__/text_processors.cpython-311.pyc ADDED
Binary file (654 Bytes). View file
 
core/__pycache__/text_processors.cpython-313.pyc ADDED
Binary file (556 Bytes). View file
 
core/__pycache__/tools.cpython-311.pyc ADDED
Binary file (41.1 kB). View file
 
core/__pycache__/tools.cpython-313.pyc ADDED
Binary file (37.5 kB). View file