Dmitry Kisselev commited on
Commit
2bded1b
·
1 Parent(s): cad6bad

Initial deployment of customer support chatbot

Browse files
Files changed (8) hide show
  1. README.md +65 -5
  2. agent.py +309 -0
  3. app.py +135 -0
  4. auth.py +67 -0
  5. config.py +20 -0
  6. mcp_client.py +81 -0
  7. memory.py +34 -0
  8. requirements.txt +5 -0
README.md CHANGED
@@ -1,13 +1,73 @@
1
  ---
2
  title: Customer Support Chatbot
3
- emoji: 📊
4
- colorFrom: red
5
- colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.1.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: Customer Support Chatbot
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Customer Support Chatbot
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.1.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Customer Support Chatbot
14
+
15
+ A prototype customer support chatbot for a computer products company, built with Gradio, OpenAI GPT-4o-mini, and MCP server integration.
16
+
17
+ ## Features
18
+
19
+ - **Product Inquiries**: Browse, search, and get details about products (no authentication required)
20
+ - **Order Management**: View and manage orders (requires authentication)
21
+ - **Session Memory**: Maintains conversation context within each session
22
+ - **Authentication**: Simple email/PIN authentication for order access
23
+
24
+ ## How to Use
25
+
26
+ ### Product Queries (No Authentication)
27
+ - "Show me monitors"
28
+ - "What products do you have?"
29
+ - "Tell me about product COM-0001"
30
+ - "Search for printers"
31
+
32
+ ### Order Queries (Requires Authentication)
33
+ - "Show my orders"
34
+ - "What's the status of my order?"
35
+ - "I want to place an order"
36
+
37
+ ### Authentication
38
+ To authenticate, include in your message:
39
+ ```
40
+ email: your@email.com, pin: 1234
41
+ ```
42
+
43
+ ## Test Customers
44
+
45
+ The following test customers are available:
46
+
47
+ - donaldgarcia@example.net / 7912
48
+ - michellejames@example.com / 1520
49
+ - laurahenderson@example.org / 1488
50
+ - spenceamanda@example.org / 2535
51
+ - glee@example.net / 4582
52
+ - williamsthomas@example.net / 4811
53
+ - justin78@example.net / 9279
54
+ - jason31@example.com / 1434
55
+ - samuel81@example.com / 4257
56
+ - williamleon@example.net / 9928
57
+
58
+ ## Architecture
59
+
60
+ - **app.py**: Gradio UI and session management
61
+ - **agent.py**: LLM agent with tool calling
62
+ - **mcp_client.py**: MCP server JSON-RPC client
63
+ - **auth.py**: Authentication handler
64
+ - **memory.py**: Session-based conversation memory
65
+ - **config.py**: Configuration management
66
+
67
+ ## Environment Variables
68
+
69
+ Set these in your HuggingFace Space settings:
70
+
71
+ - `OPENAI_API_KEY`: Your OpenAI API key
72
+ - `MCP_SERVER_URL`: MCP server URL (default: https://vipfapwm3x.us-east-1.awsapprunner.com/mcp)
73
+
agent.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LLM agent with tool calling capabilities."""
2
+ import json
3
+ from typing import Dict, Any, List, Optional
4
+ from openai import OpenAI
5
+ from config import OPENAI_API_KEY, OPENAI_MODEL
6
+ from mcp_client import MCPClient
7
+ from auth import AuthHandler
8
+
9
+
10
+ class SupportAgent:
11
+ """Customer support agent with MCP tool integration."""
12
+
13
+ def __init__(self, mcp_client: MCPClient, auth_handler: AuthHandler):
14
+ self.client = OpenAI(api_key=OPENAI_API_KEY)
15
+ self.model = OPENAI_MODEL
16
+ self.mcp_client = mcp_client
17
+ self.auth_handler = auth_handler
18
+
19
+ # Initialize MCP connection
20
+ self.mcp_client.initialize()
21
+
22
+ # Define available tools
23
+ self.tools = self._define_tools()
24
+
25
+ def _define_tools(self) -> List[Dict[str, Any]]:
26
+ """Define tool schemas for OpenAI function calling."""
27
+ return [
28
+ {
29
+ "type": "function",
30
+ "function": {
31
+ "name": "list_products",
32
+ "description": "List products with optional filters by category or active status",
33
+ "parameters": {
34
+ "type": "object",
35
+ "properties": {
36
+ "category": {
37
+ "type": "string",
38
+ "description": "Filter by category (e.g., 'Computers', 'Monitors', 'Printers')"
39
+ },
40
+ "is_active": {
41
+ "type": "boolean",
42
+ "description": "Filter by active status"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ {
49
+ "type": "function",
50
+ "function": {
51
+ "name": "get_product",
52
+ "description": "Get detailed product information by SKU",
53
+ "parameters": {
54
+ "type": "object",
55
+ "properties": {
56
+ "sku": {
57
+ "type": "string",
58
+ "description": "Product SKU (e.g., 'COM-0001', 'MON-0054')"
59
+ }
60
+ },
61
+ "required": ["sku"]
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "type": "function",
67
+ "function": {
68
+ "name": "search_products",
69
+ "description": "Search products by name or description keyword",
70
+ "parameters": {
71
+ "type": "object",
72
+ "properties": {
73
+ "query": {
74
+ "type": "string",
75
+ "description": "Search term (case-insensitive, partial match)"
76
+ }
77
+ },
78
+ "required": ["query"]
79
+ }
80
+ }
81
+ },
82
+ {
83
+ "type": "function",
84
+ "function": {
85
+ "name": "get_customer",
86
+ "description": "Get customer information by customer ID. Requires authentication.",
87
+ "parameters": {
88
+ "type": "object",
89
+ "properties": {
90
+ "customer_id": {
91
+ "type": "string",
92
+ "description": "Customer UUID"
93
+ }
94
+ },
95
+ "required": ["customer_id"]
96
+ }
97
+ }
98
+ },
99
+ {
100
+ "type": "function",
101
+ "function": {
102
+ "name": "list_orders",
103
+ "description": "List orders with optional filters. Requires authentication.",
104
+ "parameters": {
105
+ "type": "object",
106
+ "properties": {
107
+ "customer_id": {
108
+ "type": "string",
109
+ "description": "Filter by customer UUID"
110
+ },
111
+ "status": {
112
+ "type": "string",
113
+ "description": "Filter by status: draft, submitted, approved, fulfilled, cancelled"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ },
119
+ {
120
+ "type": "function",
121
+ "function": {
122
+ "name": "get_order",
123
+ "description": "Get detailed order information including items. Requires authentication.",
124
+ "parameters": {
125
+ "type": "object",
126
+ "properties": {
127
+ "order_id": {
128
+ "type": "string",
129
+ "description": "Order UUID"
130
+ }
131
+ },
132
+ "required": ["order_id"]
133
+ }
134
+ }
135
+ },
136
+ {
137
+ "type": "function",
138
+ "function": {
139
+ "name": "create_order",
140
+ "description": "Create a new order with items. Requires authentication.",
141
+ "parameters": {
142
+ "type": "object",
143
+ "properties": {
144
+ "customer_id": {
145
+ "type": "string",
146
+ "description": "Customer UUID"
147
+ },
148
+ "items": {
149
+ "type": "array",
150
+ "description": "List of order items",
151
+ "items": {
152
+ "type": "object",
153
+ "properties": {
154
+ "sku": {"type": "string"},
155
+ "quantity": {"type": "integer"},
156
+ "unit_price": {"type": "string"},
157
+ "currency": {"type": "string", "default": "USD"}
158
+ },
159
+ "required": ["sku", "quantity", "unit_price"]
160
+ }
161
+ }
162
+ },
163
+ "required": ["customer_id", "items"]
164
+ }
165
+ }
166
+ }
167
+ ]
168
+
169
+ def _requires_auth(self, tool_name: str) -> bool:
170
+ """Check if tool requires authentication."""
171
+ auth_required_tools = ["get_customer", "list_orders", "get_order", "create_order"]
172
+ return tool_name in auth_required_tools
173
+
174
+ def _get_customer_id(self, session_id: str) -> Optional[str]:
175
+ """Get customer_id from authenticated session."""
176
+ if not self.auth_handler.is_authenticated(session_id):
177
+ return None
178
+ return self.auth_handler.get_customer_id(session_id)
179
+
180
+ def process_message(self, session_id: str, user_message: str, conversation_history: List[Dict[str, str]]) -> str:
181
+ """Process user message and return response."""
182
+ # Check if message is about authentication
183
+ if "email" in user_message.lower() and "pin" in user_message.lower():
184
+ # Try to extract email and PIN from message
185
+ # This is a simple approach - in production, use structured input
186
+ return "To authenticate, please provide your email and PIN in the format: 'email: your@email.com, pin: 1234'"
187
+
188
+ # Build system message with authentication status
189
+ is_authenticated = self.auth_handler.is_authenticated(session_id)
190
+ auth_status = "authenticated" if is_authenticated else "not authenticated"
191
+ customer_email = self.auth_handler.get_email(session_id) if is_authenticated else None
192
+
193
+ system_content = """You are a helpful customer support agent for a computer products company.
194
+ You can help customers with:
195
+ - Product inquiries (browsing, searching, getting details) - no authentication needed
196
+ - Order management (viewing orders, order status, placing orders) - requires authentication
197
+
198
+ Current session status: """ + auth_status
199
+ if customer_email:
200
+ system_content += f"\nAuthenticated customer: {customer_email}"
201
+ system_content += """
202
+
203
+ When a customer asks about orders and is not authenticated, ask them to provide their email and PIN.
204
+ If the customer is already authenticated, you can directly help with their orders.
205
+ Be friendly, professional, and helpful. Provide clear, concise answers."""
206
+
207
+ messages = [
208
+ {
209
+ "role": "system",
210
+ "content": system_content
211
+ }
212
+ ]
213
+
214
+ # Add conversation history
215
+ messages.extend(conversation_history)
216
+
217
+ # Add current user message
218
+ messages.append({"role": "user", "content": user_message})
219
+
220
+ # Check if query might need authentication
221
+ order_keywords = ["order", "purchase", "buy", "my orders", "order history", "track order", "place order"]
222
+ needs_auth = any(keyword in user_message.lower() for keyword in order_keywords)
223
+
224
+ if needs_auth and not self.auth_handler.is_authenticated(session_id):
225
+ return "To access your orders, I need to verify your identity. Please provide your email and PIN in this format: 'email: your@email.com, pin: 1234'"
226
+
227
+ try:
228
+ # Call OpenAI with tool calling
229
+ response = self.client.chat.completions.create(
230
+ model=self.model,
231
+ messages=messages,
232
+ tools=self.tools,
233
+ tool_choice="auto"
234
+ )
235
+
236
+ message = response.choices[0].message
237
+
238
+ # Handle tool calls
239
+ if message.tool_calls:
240
+ tool_results = []
241
+ for tool_call in message.tool_calls:
242
+ tool_name = tool_call.function.name
243
+ tool_args = json.loads(tool_call.function.arguments)
244
+
245
+ # Check authentication for order-related tools
246
+ if self._requires_auth(tool_name) and not self.auth_handler.is_authenticated(session_id):
247
+ tool_results.append({
248
+ "role": "tool",
249
+ "tool_call_id": tool_call.id,
250
+ "name": tool_name,
251
+ "content": "Authentication required. Please provide your email and PIN."
252
+ })
253
+ continue
254
+
255
+ # Inject customer_id for order-related tools if not provided
256
+ if self._requires_auth(tool_name):
257
+ customer_id = self._get_customer_id(session_id)
258
+ if customer_id:
259
+ # Add customer_id to tool args if not present and tool needs it
260
+ if tool_name in ["list_orders", "get_customer", "create_order"]:
261
+ if "customer_id" not in tool_args or not tool_args.get("customer_id"):
262
+ tool_args["customer_id"] = customer_id
263
+ elif tool_name == "list_orders":
264
+ # For list_orders, if no customer_id, we can still call it without filter
265
+ # The MCP server will handle it
266
+ pass
267
+
268
+ # Call MCP tool
269
+ try:
270
+ result = self.mcp_client.call_tool(tool_name, tool_args)
271
+
272
+ # Extract text content from result
273
+ if "content" in result and len(result["content"]) > 0:
274
+ content = result["content"][0].get("text", str(result))
275
+ elif "structuredContent" in result:
276
+ content = result["structuredContent"].get("result", str(result))
277
+ else:
278
+ content = str(result)
279
+
280
+ tool_results.append({
281
+ "role": "tool",
282
+ "tool_call_id": tool_call.id,
283
+ "name": tool_name,
284
+ "content": content
285
+ })
286
+ except Exception as e:
287
+ tool_results.append({
288
+ "role": "tool",
289
+ "tool_call_id": tool_call.id,
290
+ "name": tool_name,
291
+ "content": f"Error: {str(e)}"
292
+ })
293
+
294
+ # Get final response with tool results
295
+ messages.append(message)
296
+ messages.extend(tool_results)
297
+
298
+ final_response = self.client.chat.completions.create(
299
+ model=self.model,
300
+ messages=messages
301
+ )
302
+
303
+ return final_response.choices[0].message.content
304
+ else:
305
+ return message.content
306
+
307
+ except Exception as e:
308
+ return f"I apologize, but I encountered an error: {str(e)}. Please try again."
309
+
app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio UI for customer support chatbot."""
2
+ import gradio as gr
3
+ import re
4
+ from typing import Optional
5
+ from agent import SupportAgent
6
+ from mcp_client import MCPClient
7
+ from auth import AuthHandler
8
+ from memory import SessionMemory
9
+
10
+
11
+ # Initialize components
12
+ mcp_client = MCPClient()
13
+ auth_handler = AuthHandler(mcp_client)
14
+ memory = SessionMemory()
15
+ agent = SupportAgent(mcp_client, auth_handler)
16
+
17
+
18
+ def parse_auth(message: str) -> tuple[Optional[str], Optional[str]]:
19
+ """Parse email and PIN from message."""
20
+ email_pattern = r'email:\s*([^\s,]+)'
21
+ pin_pattern = r'pin:\s*(\d{4})'
22
+
23
+ email_match = re.search(email_pattern, message, re.IGNORECASE)
24
+ pin_match = re.search(pin_pattern, message, re.IGNORECASE)
25
+
26
+ email = email_match.group(1) if email_match else None
27
+ pin = pin_match.group(1) if pin_match else None
28
+
29
+ return email, pin
30
+
31
+
32
+ def chat_response(message, history, session_id):
33
+ """Handle chat message and return response."""
34
+ if not message:
35
+ return history, ""
36
+
37
+ # Check if message contains authentication
38
+ email, pin = parse_auth(message)
39
+ if email and pin:
40
+ # Attempt authentication
41
+ success, msg = auth_handler.authenticate(session_id, email, pin)
42
+ if success:
43
+ response = "✅ Authentication successful! How can I help you today?"
44
+ else:
45
+ response = f"❌ Authentication failed: {msg}. Please check your email and PIN and try again."
46
+
47
+ # Gradio 6.x format: list of dicts with role and content
48
+ history.append({"role": "user", "content": message})
49
+ history.append({"role": "assistant", "content": response})
50
+ memory.add_message(session_id, "user", message)
51
+ memory.add_message(session_id, "assistant", response)
52
+ return history, ""
53
+
54
+ # Get conversation history
55
+ conv_history = memory.get_conversation_context(session_id)
56
+
57
+ # Process message with agent
58
+ response = agent.process_message(session_id, message, conv_history)
59
+
60
+ # Update history - Gradio 6.x format
61
+ history.append({"role": "user", "content": message})
62
+ history.append({"role": "assistant", "content": response})
63
+ memory.add_message(session_id, "user", message)
64
+ memory.add_message(session_id, "assistant", response)
65
+
66
+ return history, ""
67
+
68
+
69
+ def create_interface():
70
+ """Create Gradio interface."""
71
+ with gr.Blocks() as demo:
72
+ gr.Markdown("# 🛒 Customer Support Chatbot")
73
+ gr.Markdown("Welcome! I can help you with product inquiries and order management.")
74
+ gr.Markdown("**Note:** For order-related queries, you'll need to authenticate with your email and PIN.")
75
+
76
+ chatbot = gr.Chatbot(
77
+ label="Chat",
78
+ height=500
79
+ )
80
+
81
+ msg = gr.Textbox(
82
+ label="Your Message",
83
+ placeholder="Type your message here...",
84
+ lines=2
85
+ )
86
+
87
+ with gr.Row():
88
+ submit_btn = gr.Button("Send", variant="primary")
89
+ clear_btn = gr.Button("Clear Chat")
90
+
91
+ gr.Markdown("### Authentication")
92
+ gr.Markdown("To authenticate, include in your message: `email: your@email.com, pin: 1234`")
93
+
94
+ # Session state
95
+ session_id = gr.State(value="default_session")
96
+
97
+ # Event handlers
98
+ def submit_message(message, history, session):
99
+ history, _ = chat_response(message, history, session)
100
+ return history, ""
101
+
102
+ def clear_chat(session):
103
+ memory.clear(session)
104
+ auth_handler.clear_auth(session)
105
+ return [] # Return empty list for Gradio 6.x
106
+
107
+ submit_btn.click(
108
+ submit_message,
109
+ inputs=[msg, chatbot, session_id],
110
+ outputs=[chatbot, msg]
111
+ )
112
+
113
+ msg.submit(
114
+ submit_message,
115
+ inputs=[msg, chatbot, session_id],
116
+ outputs=[chatbot, msg]
117
+ )
118
+
119
+ clear_btn.click(
120
+ clear_chat,
121
+ inputs=[session_id],
122
+ outputs=[chatbot]
123
+ )
124
+
125
+ return demo
126
+
127
+
128
+ if __name__ == "__main__":
129
+ demo = create_interface()
130
+ demo.launch(
131
+ server_name="0.0.0.0",
132
+ server_port=7860,
133
+ share=False
134
+ )
135
+
auth.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Authentication handler for customer sessions."""
2
+ import re
3
+ from typing import Dict, Optional, Any
4
+ from mcp_client import MCPClient
5
+
6
+
7
+ class AuthHandler:
8
+ """Manages customer authentication state per session."""
9
+
10
+ def __init__(self, mcp_client: MCPClient):
11
+ self.mcp_client = mcp_client
12
+ self.auth_state: Dict[str, Dict[str, Any]] = {}
13
+
14
+ def authenticate(self, session_id: str, email: str, pin: str) -> tuple[bool, str]:
15
+ """Authenticate customer and store session state."""
16
+ try:
17
+ result = self.mcp_client.verify_customer(email, pin)
18
+
19
+ # Extract customer_id from result
20
+ # The result contains formatted text with customer details
21
+ # We'll extract customer_id from the text or structured content
22
+ customer_id = None
23
+ customer_info_text = ""
24
+
25
+ if "content" in result and len(result["content"]) > 0:
26
+ customer_info_text = result["content"][0].get("text", "")
27
+ elif "structuredContent" in result:
28
+ customer_info_text = result["structuredContent"].get("result", "")
29
+
30
+ # Try to extract customer_id from text (format: "Customer ID: <uuid>" or similar)
31
+ uuid_pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
32
+ matches = re.findall(uuid_pattern, customer_info_text, re.IGNORECASE)
33
+ if matches:
34
+ customer_id = matches[0]
35
+
36
+ self.auth_state[session_id] = {
37
+ "email": email,
38
+ "authenticated": True,
39
+ "customer_id": customer_id,
40
+ "customer_info": result,
41
+ "customer_info_text": customer_info_text
42
+ }
43
+ return True, "Authentication successful"
44
+ except Exception as e:
45
+ return False, str(e)
46
+
47
+ def is_authenticated(self, session_id: str) -> bool:
48
+ """Check if session is authenticated."""
49
+ return self.auth_state.get(session_id, {}).get("authenticated", False)
50
+
51
+ def get_email(self, session_id: str) -> Optional[str]:
52
+ """Get authenticated email for session."""
53
+ return self.auth_state.get(session_id, {}).get("email")
54
+
55
+ def get_customer_info(self, session_id: str) -> Optional[Dict[str, Any]]:
56
+ """Get customer info for authenticated session."""
57
+ return self.auth_state.get(session_id, {}).get("customer_info")
58
+
59
+ def get_customer_id(self, session_id: str) -> Optional[str]:
60
+ """Get customer ID for authenticated session."""
61
+ return self.auth_state.get(session_id, {}).get("customer_id")
62
+
63
+ def clear_auth(self, session_id: str):
64
+ """Clear authentication for session."""
65
+ if session_id in self.auth_state:
66
+ del self.auth_state[session_id]
67
+
config.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration management for the customer support chatbot."""
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ # OpenAI Configuration
8
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
9
+ if not OPENAI_API_KEY:
10
+ raise ValueError("OPENAI_API_KEY not found in environment variables")
11
+
12
+ # MCP Server Configuration
13
+ MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "https://vipfapwm3x.us-east-1.awsapprunner.com/mcp")
14
+
15
+ # HuggingFace Configuration (for deployment)
16
+ HF_TOKEN = os.getenv("HF_TOKEN", "")
17
+
18
+ # OpenAI Model
19
+ OPENAI_MODEL = "gpt-4o-mini"
20
+
mcp_client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MCP server client for JSON-RPC communication."""
2
+ import requests
3
+ import json
4
+ from typing import Dict, Any, Optional
5
+ from config import MCP_SERVER_URL
6
+
7
+
8
+ class MCPClient:
9
+ """Client for communicating with MCP server via JSON-RPC 2.0."""
10
+
11
+ def __init__(self):
12
+ self.url = MCP_SERVER_URL
13
+ self.request_id = 0
14
+ self._initialized = False
15
+
16
+ def _get_next_id(self) -> int:
17
+ """Get next request ID."""
18
+ self.request_id += 1
19
+ return self.request_id
20
+
21
+ def _call(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
22
+ """Make JSON-RPC call to MCP server."""
23
+ payload = {
24
+ "jsonrpc": "2.0",
25
+ "id": self._get_next_id(),
26
+ "method": method
27
+ }
28
+ if params:
29
+ payload["params"] = params
30
+
31
+ headers = {
32
+ "Content-Type": "application/json",
33
+ "Accept": "application/json"
34
+ }
35
+
36
+ try:
37
+ response = requests.post(self.url, json=payload, headers=headers, timeout=10)
38
+ response.raise_for_status()
39
+ result = response.json()
40
+
41
+ if "error" in result:
42
+ raise Exception(f"MCP Error: {result['error'].get('message', 'Unknown error')}")
43
+
44
+ return result.get("result", {})
45
+ except requests.exceptions.RequestException as e:
46
+ raise Exception(f"Failed to communicate with MCP server: {str(e)}")
47
+
48
+ def initialize(self):
49
+ """Initialize MCP connection."""
50
+ if self._initialized:
51
+ return
52
+
53
+ result = self._call("initialize", {
54
+ "protocolVersion": "2024-11-05",
55
+ "capabilities": {},
56
+ "clientInfo": {
57
+ "name": "customer-support-chatbot",
58
+ "version": "1.0.0"
59
+ }
60
+ })
61
+ self._initialized = True
62
+ return result
63
+
64
+ def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
65
+ """Call an MCP tool."""
66
+ if not self._initialized:
67
+ self.initialize()
68
+
69
+ result = self._call("tools/call", {
70
+ "name": tool_name,
71
+ "arguments": arguments
72
+ })
73
+ return result
74
+
75
+ def verify_customer(self, email: str, pin: str) -> Dict[str, Any]:
76
+ """Verify customer with email and PIN."""
77
+ return self.call_tool("verify_customer_pin", {
78
+ "email": email,
79
+ "pin": pin
80
+ })
81
+
memory.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Session-based conversation memory manager."""
2
+ from typing import List, Dict, Any
3
+
4
+
5
+ class SessionMemory:
6
+ """Manages conversation memory per session."""
7
+
8
+ def __init__(self):
9
+ self.memories: Dict[str, List[Dict[str, str]]] = {}
10
+
11
+ def get_messages(self, session_id: str) -> List[Dict[str, str]]:
12
+ """Get conversation history for a session."""
13
+ return self.memories.get(session_id, [])
14
+
15
+ def add_message(self, session_id: str, role: str, content: str):
16
+ """Add a message to session memory."""
17
+ if session_id not in self.memories:
18
+ self.memories[session_id] = []
19
+
20
+ self.memories[session_id].append({
21
+ "role": role,
22
+ "content": content
23
+ })
24
+
25
+ def clear(self, session_id: str):
26
+ """Clear memory for a session."""
27
+ if session_id in self.memories:
28
+ del self.memories[session_id]
29
+
30
+ def get_conversation_context(self, session_id: str, max_messages: int = 10) -> List[Dict[str, str]]:
31
+ """Get recent conversation context."""
32
+ messages = self.get_messages(session_id)
33
+ return messages[-max_messages:] if len(messages) > max_messages else messages
34
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ openai>=1.0.0
3
+ python-dotenv>=1.0.0
4
+ requests>=2.31.0
5
+