Tripagra commited on
Commit
e3cbe6c
·
1 Parent(s): bdf0486

Fix Vercel deployment: Use proper serverless handler and streamline dependencies

Browse files
Files changed (3) hide show
  1. api/ask.py +89 -84
  2. requirements.txt +1 -11
  3. vercel.json +1 -6
api/ask.py CHANGED
@@ -3,10 +3,8 @@ Vercel Serverless Function for handling customer support questions.
3
  Endpoint: POST /api/ask
4
  """
5
 
6
- from fastapi import FastAPI, Request
7
- from fastapi.responses import JSONResponse
8
- from fastapi.middleware.cors import CORSMiddleware
9
- from pydantic import BaseModel
10
  import sys
11
  import os
12
 
@@ -19,16 +17,17 @@ from src.llm_client import LLMClient
19
  from src.config import Settings
20
 
21
  # Initialize components (cached across invocations)
22
- settings = Settings()
23
  rag_system = None
24
  llm_client = None
25
  escalation_engine = None
26
 
27
  def initialize_components():
28
  """Initialize RAG system, LLM client, and escalation engine."""
29
- global rag_system, llm_client, escalation_engine
30
 
31
  if rag_system is None:
 
32
  articles_dir = os.path.join(
33
  os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
34
  "data",
@@ -41,88 +40,94 @@ def initialize_components():
41
  relevance_threshold=settings.relevance_threshold
42
  )
43
 
44
- app = FastAPI(title="Smart Escalation API")
45
-
46
- # Configure CORS
47
- app.add_middleware(
48
- CORSMiddleware,
49
- allow_origins=["*"], # In production, restrict to your domain
50
- allow_credentials=True,
51
- allow_methods=["*"],
52
- allow_headers=["*"],
53
- )
54
-
55
- class QuestionRequest(BaseModel):
56
- question: str
57
-
58
- class QuestionResponse(BaseModel):
59
- response_type: str
60
- message: str
61
- confidence_explanation: str
62
- sources: list = None
63
-
64
- @app.post("/api/ask", response_model=QuestionResponse)
65
- async def ask_question(request: QuestionRequest):
66
- """
67
- Process customer question and return answer or escalation.
68
 
69
- Args:
70
- request: QuestionRequest with 'question' field
71
-
72
- Returns:
73
- QuestionResponse with response_type, message, confidence_explanation, sources
74
- """
75
- try:
76
- # Initialize components on first request
77
- initialize_components()
78
-
79
- # Validate question
80
- if not request.question or len(request.question.strip()) == 0:
81
- return JSONResponse(
82
- status_code=400,
83
- content={
84
- "response_type": "error",
85
- "message": "Question cannot be empty",
86
- "confidence_explanation": "Invalid request",
87
- "sources": None
88
- }
89
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- # Retrieve relevant chunks
92
- chunks = rag_system.retrieve(request.question, top_k=settings.top_k_chunks)
 
 
93
 
94
- # Decide whether to answer or escalate
95
- decision = escalation_engine.process_question(request.question, chunks)
 
 
 
 
 
 
96
 
97
- return {
98
- "response_type": decision.action,
99
- "message": decision.message,
100
- "confidence_explanation": decision.confidence_explanation,
101
- "sources": decision.sources
102
  }
103
 
104
- except Exception as e:
105
- # Log error and return escalation response
106
- print(f"Error processing question: {str(e)}")
107
- return JSONResponse(
108
- status_code=500,
109
- content={
110
- "response_type": "escalation",
111
- "message": "I'm experiencing technical difficulties. Let me connect you with a human agent.",
112
- "confidence_explanation": f"System error occurred",
113
- "sources": None
114
- }
115
- )
116
-
117
- @app.get("/api/health")
118
- async def health_check():
119
- """Health check endpoint."""
120
- return {
121
- "status": "healthy",
122
- "vector_store_loaded": rag_system is not None
123
- }
124
 
125
- # Vercel serverless function handler
126
- def handler(request: Request):
127
- """Main handler for Vercel serverless function."""
128
- return app(request)
 
3
  Endpoint: POST /api/ask
4
  """
5
 
6
+ from http.server import BaseHTTPRequestHandler
7
+ import json
 
 
8
  import sys
9
  import os
10
 
 
17
  from src.config import Settings
18
 
19
  # Initialize components (cached across invocations)
20
+ settings = None
21
  rag_system = None
22
  llm_client = None
23
  escalation_engine = None
24
 
25
  def initialize_components():
26
  """Initialize RAG system, LLM client, and escalation engine."""
27
+ global settings, rag_system, llm_client, escalation_engine
28
 
29
  if rag_system is None:
30
+ settings = Settings()
31
  articles_dir = os.path.join(
32
  os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
33
  "data",
 
40
  relevance_threshold=settings.relevance_threshold
41
  )
42
 
43
+ class handler(BaseHTTPRequestHandler):
44
+ """Vercel serverless function handler."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ def do_POST(self):
47
+ """Handle POST requests."""
48
+ try:
49
+ # Initialize components on first request
50
+ initialize_components()
51
+
52
+ # Read request body
53
+ content_length = int(self.headers.get('Content-Length', 0))
54
+ body = self.rfile.read(content_length).decode('utf-8')
55
+
56
+ # Parse JSON
57
+ try:
58
+ data = json.loads(body)
59
+ question = data.get('question', '').strip()
60
+ except json.JSONDecodeError:
61
+ self.send_error_response(400, "Invalid JSON")
62
+ return
63
+
64
+ # Validate question
65
+ if not question:
66
+ self.send_error_response(400, "Question cannot be empty")
67
+ return
68
+
69
+ # Retrieve relevant chunks
70
+ chunks = rag_system.retrieve(question, top_k=settings.top_k_chunks)
71
+
72
+ # Decide whether to answer or escalate
73
+ decision = escalation_engine.process_question(question, chunks)
74
+
75
+ # Send success response
76
+ self.send_response(200)
77
+ self.send_header('Content-Type', 'application/json')
78
+ self.send_header('Access-Control-Allow-Origin', '*')
79
+ self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
80
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
81
+ self.end_headers()
82
+
83
+ response = {
84
+ "response_type": decision.action,
85
+ "message": decision.message,
86
+ "confidence_explanation": decision.confidence_explanation,
87
+ "sources": decision.sources
88
+ }
89
+
90
+ self.wfile.write(json.dumps(response).encode('utf-8'))
91
+
92
+ except Exception as e:
93
+ print(f"Error processing question: {str(e)}")
94
+ self.send_error_response(500, "Internal server error")
95
+
96
+ def do_OPTIONS(self):
97
+ """Handle OPTIONS requests for CORS."""
98
+ self.send_response(200)
99
+ self.send_header('Access-Control-Allow-Origin', '*')
100
+ self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
101
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
102
+ self.end_headers()
103
+
104
+ def do_GET(self):
105
+ """Handle GET requests (health check)."""
106
+ self.send_response(200)
107
+ self.send_header('Content-Type', 'application/json')
108
+ self.send_header('Access-Control-Allow-Origin', '*')
109
+ self.end_headers()
110
 
111
+ response = {
112
+ "status": "healthy",
113
+ "vector_store_loaded": rag_system is not None
114
+ }
115
 
116
+ self.wfile.write(json.dumps(response).encode('utf-8'))
117
+
118
+ def send_error_response(self, status_code, message):
119
+ """Send error response."""
120
+ self.send_response(status_code)
121
+ self.send_header('Content-Type', 'application/json')
122
+ self.send_header('Access-Control-Allow-Origin', '*')
123
+ self.end_headers()
124
 
125
+ response = {
126
+ "response_type": "error",
127
+ "message": message,
128
+ "confidence_explanation": "Request error",
129
+ "sources": None
130
  }
131
 
132
+ self.wfile.write(json.dumps(response).encode('utf-8'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,4 @@
1
- # FastAPI and server dependencies
2
- fastapi==0.104.1
3
- uvicorn[standard]==0.24.0
4
  pydantic==2.5.0
5
  pydantic-settings==2.1.0
6
  python-dotenv==1.0.0
@@ -13,11 +11,3 @@ numpy==1.24.3
13
  # LLM API clients
14
  google-generativeai==0.3.2
15
 
16
- # Testing dependencies
17
- pytest==7.4.3
18
- pytest-asyncio==0.21.1
19
- hypothesis==6.92.1
20
- httpx==0.25.2
21
-
22
- # CORS support
23
- python-multipart==0.0.6
 
1
+ # Core dependencies
 
 
2
  pydantic==2.5.0
3
  pydantic-settings==2.1.0
4
  python-dotenv==1.0.0
 
11
  # LLM API clients
12
  google-generativeai==0.3.2
13
 
 
 
 
 
 
 
 
 
vercel.json CHANGED
@@ -1,10 +1,5 @@
1
  {
2
  "buildCommand": "cd frontend && npm ci && npm run build",
3
  "outputDirectory": "frontend/dist",
4
- "installCommand": "pip install -r requirements.txt",
5
- "functions": {
6
- "api/**/*.py": {
7
- "runtime": "@vercel/python@4.3.0"
8
- }
9
- }
10
  }
 
1
  {
2
  "buildCommand": "cd frontend && npm ci && npm run build",
3
  "outputDirectory": "frontend/dist",
4
+ "installCommand": "pip install --upgrade pip && pip install -r requirements.txt"
 
 
 
 
 
5
  }