Tripagra commited on
Commit
6aa1546
·
1 Parent(s): d11bdce

Add api/index.py for Vercel serverless function and update frontend endpoint

Browse files
Files changed (2) hide show
  1. api/index.py +136 -0
  2. frontend/src/services/api.ts +2 -2
api/index.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vercel Serverless Function - Main API endpoint
3
+ Handles: POST /api
4
+ """
5
+
6
+ from http.server import BaseHTTPRequestHandler
7
+ import json
8
+ import sys
9
+ import os
10
+
11
+ # Add parent directory to path
12
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13
+
14
+ from src.rag import RAGSystem
15
+ from src.escalation import EscalationEngine
16
+ from src.llm_client import LLMClient
17
+ from src.config import Settings
18
+
19
+ # Global components (cached across invocations)
20
+ _settings = None
21
+ _rag_system = None
22
+ _llm_client = None
23
+ _escalation_engine = None
24
+
25
+ def get_components():
26
+ """Initialize and return components (cached)."""
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",
34
+ "articles"
35
+ )
36
+ _rag_system = RAGSystem(articles_dir=articles_dir)
37
+ _llm_client = LLMClient(api_key=_settings.google_api_key, model=_settings.llm_model)
38
+ _escalation_engine = EscalationEngine(
39
+ llm_client=_llm_client,
40
+ relevance_threshold=_settings.relevance_threshold
41
+ )
42
+
43
+ return _settings, _rag_system, _llm_client, _escalation_engine
44
+
45
+ class handler(BaseHTTPRequestHandler):
46
+ """Vercel serverless function handler."""
47
+
48
+ def do_POST(self):
49
+ """Handle POST requests."""
50
+ try:
51
+ settings, rag_system, llm_client, escalation_engine = get_components()
52
+
53
+ # Read request body
54
+ content_length = int(self.headers.get('Content-Length', 0))
55
+ body = self.rfile.read(content_length).decode('utf-8')
56
+
57
+ # Parse JSON
58
+ try:
59
+ data = json.loads(body)
60
+ question = data.get('question', '').strip()
61
+ except json.JSONDecodeError:
62
+ self.send_error_response(400, "Invalid JSON")
63
+ return
64
+
65
+ # Validate question
66
+ if not question:
67
+ self.send_error_response(400, "Question cannot be empty")
68
+ return
69
+
70
+ # Retrieve relevant chunks
71
+ chunks = rag_system.retrieve(question, top_k=settings.top_k_chunks)
72
+
73
+ # Decide whether to answer or escalate
74
+ decision = escalation_engine.process_question(question, chunks)
75
+
76
+ # Send success response
77
+ self.send_response(200)
78
+ self.send_header('Content-Type', 'application/json')
79
+ self.send_header('Access-Control-Allow-Origin', '*')
80
+ self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
81
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
82
+ self.end_headers()
83
+
84
+ response = {
85
+ "response_type": decision.action,
86
+ "message": decision.message,
87
+ "confidence_explanation": decision.confidence_explanation,
88
+ "sources": decision.sources
89
+ }
90
+
91
+ self.wfile.write(json.dumps(response).encode('utf-8'))
92
+
93
+ except Exception as e:
94
+ print(f"Error processing question: {str(e)}")
95
+ import traceback
96
+ traceback.print_exc()
97
+ self.send_error_response(500, "Internal server error")
98
+
99
+ def do_OPTIONS(self):
100
+ """Handle OPTIONS requests for CORS."""
101
+ self.send_response(200)
102
+ self.send_header('Access-Control-Allow-Origin', '*')
103
+ self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
104
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
105
+ self.end_headers()
106
+
107
+ def do_GET(self):
108
+ """Handle GET requests (health check)."""
109
+ self.send_response(200)
110
+ self.send_header('Content-Type', 'application/json')
111
+ self.send_header('Access-Control-Allow-Origin', '*')
112
+ self.end_headers()
113
+
114
+ response = {
115
+ "status": "healthy",
116
+ "message": "Smart Escalation API is running",
117
+ "vector_store_loaded": _rag_system is not None
118
+ }
119
+
120
+ self.wfile.write(json.dumps(response).encode('utf-8'))
121
+
122
+ def send_error_response(self, status_code, message):
123
+ """Send error response."""
124
+ self.send_response(status_code)
125
+ self.send_header('Content-Type', 'application/json')
126
+ self.send_header('Access-Control-Allow-Origin', '*')
127
+ self.end_headers()
128
+
129
+ response = {
130
+ "response_type": "error",
131
+ "message": message,
132
+ "confidence_explanation": "Request error",
133
+ "sources": None
134
+ }
135
+
136
+ self.wfile.write(json.dumps(response).encode('utf-8'))
frontend/src/services/api.ts CHANGED
@@ -45,7 +45,7 @@ const getApiUrl = (): string => {
45
  };
46
 
47
  /**
48
- * POST a question to the /ask endpoint
49
  *
50
  * @param question - The customer question to submit
51
  * @returns Promise resolving to the API response
@@ -53,7 +53,7 @@ const getApiUrl = (): string => {
53
  */
54
  export const askQuestion = async (question: string): Promise<ApiResponse> => {
55
  const apiUrl = getApiUrl();
56
- const endpoint = `${apiUrl}/ask`;
57
 
58
  // Validate input
59
  if (!question || question.trim().length === 0) {
 
45
  };
46
 
47
  /**
48
+ * POST a question to the API endpoint
49
  *
50
  * @param question - The customer question to submit
51
  * @returns Promise resolving to the API response
 
53
  */
54
  export const askQuestion = async (question: string): Promise<ApiResponse> => {
55
  const apiUrl = getApiUrl();
56
+ const endpoint = apiUrl; // Use /api directly (not /api/ask)
57
 
58
  // Validate input
59
  if (!question || question.trim().length === 0) {