PD03 commited on
Commit
26fc8f8
Β·
verified Β·
1 Parent(s): 88374e6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -103
app.py CHANGED
@@ -6,6 +6,7 @@ import time
6
  from datetime import datetime, timedelta
7
  import logging
8
  from typing import Dict, Any, Optional, List
 
9
 
10
  # Configure logging
11
  logging.basicConfig(level=logging.INFO)
@@ -27,16 +28,20 @@ class MCPSAPClient:
27
  self.session.timeout = 30
28
  self.max_retries = 3
29
  self.retry_delay = 2
30
-
 
 
 
 
 
31
  def query_llm_with_fallback(self, prompt: str, max_length: int = 150) -> str:
32
- """Query LLM with fallback options and retry logic"""
33
  apis_to_try = [API_URL] + FALLBACK_APIS
34
-
35
  for api_url in apis_to_try:
36
  for attempt in range(self.max_retries):
37
  try:
38
  logger.info(f"Attempting LLM query with {api_url}, attempt {attempt + 1}")
39
-
40
  # Different payload formats for different APIs
41
  if "DialoGPT" in api_url:
42
  payload = {
@@ -55,16 +60,15 @@ class MCPSAPClient:
55
  "temperature": 0.3
56
  }
57
  }
58
-
59
  response = self.session.post(
60
  api_url,
61
  json=payload,
62
  timeout=30
63
  )
64
-
65
  if response.status_code == 200:
66
  result = response.json()
67
-
68
  # Handle different response formats
69
  if isinstance(result, list) and len(result) > 0:
70
  if "generated_text" in result[0]:
@@ -76,9 +80,7 @@ class MCPSAPClient:
76
  return result["generated_text"]
77
  elif "text" in result:
78
  return result["text"]
79
-
80
  return str(result)
81
-
82
  elif response.status_code == 503:
83
  logger.warning(f"Model loading, waiting {self.retry_delay} seconds...")
84
  time.sleep(self.retry_delay)
@@ -87,73 +89,62 @@ class MCPSAPClient:
87
  logger.warning(f"API returned status {response.status_code}")
88
  time.sleep(self.retry_delay)
89
  continue
90
-
91
  except requests.exceptions.RequestException as e:
92
  logger.error(f"Request failed: {e}")
93
  if attempt < self.max_retries - 1:
94
  time.sleep(self.retry_delay)
95
  continue
96
-
97
  except Exception as e:
98
  logger.error(f"Unexpected error: {e}")
99
  if attempt < self.max_retries - 1:
100
  time.sleep(self.retry_delay)
101
  continue
102
-
103
  logger.warning(f"All attempts failed for {api_url}, trying next API...")
104
-
105
  # If all APIs fail, return a fallback response
106
  return "Unable to process with LLM, using fallback parsing"
107
-
108
  def extract_query_params_robust(self, query: str) -> Dict[str, Any]:
109
- """Extract parameters with multiple strategies"""
110
-
111
- # Strategy 1: Try LLM extraction
112
  try:
113
  prompt = f"""
114
  Extract date and quantity from this query: '{query}'
115
  Return only JSON format:
116
  {{"date_from":"YYYY-MM-DD","min_quantity":number}}
117
-
118
  Examples:
119
  - "orders after 2023-04-01 with at least 10 units" -> {{"date_from":"2023-04-01","min_quantity":10}}
120
  - "show me orders from last month" -> {{"date_from":"2023-11-01","min_quantity":0}}
121
  """
122
-
123
  llm_response = self.query_llm_with_fallback(prompt)
124
  logger.info(f"LLM Response: {llm_response}")
125
-
126
- # Try to extract JSON from response
127
- json_match = re.search(r'\\{[^}]*\\}', llm_response, re.DOTALL)
128
  if json_match:
129
  json_text = json_match.group()
130
  parsed = json.loads(json_text)
131
  if "date_from" in parsed and "min_quantity" in parsed:
132
  return parsed
133
-
134
  except Exception as e:
135
  logger.warning(f"LLM extraction failed: {e}")
136
-
137
- # Strategy 2: Rule-based extraction
138
  return self.extract_params_rule_based(query)
139
-
140
  def extract_params_rule_based(self, query: str) -> Dict[str, Any]:
141
- """Fallback rule-based parameter extraction"""
142
  params = {"date_from": "2023-01-01", "min_quantity": 0}
143
-
144
- # Date extraction patterns
145
  date_patterns = [
146
- r'(\\d{4}-\\d{2}-\\d{2})', # YYYY-MM-DD
147
- r'(\\d{2}/\\d{2}/\\d{4})', # MM/DD/YYYY
148
- r'(\\d{1,2}/\\d{1,2}/\\d{4})', # M/D/YYYY
149
  ]
150
-
151
  for pattern in date_patterns:
152
  match = re.search(pattern, query)
153
  if match:
154
  date_str = match.group(1)
155
  try:
156
- # Convert to YYYY-MM-DD format
157
  if '-' in date_str:
158
  params["date_from"] = date_str
159
  elif '/' in date_str:
@@ -163,16 +154,14 @@ class MCPSAPClient:
163
  break
164
  except:
165
  continue
166
-
167
- # Quantity extraction
168
  quantity_patterns = [
169
- r'at least (\\d+)',
170
- r'minimum (\\d+)',
171
- r'more than (\\d+)',
172
- r'> ?(\\d+)',
173
- r'greater than (\\d+)'
174
  ]
175
-
176
  for pattern in quantity_patterns:
177
  match = re.search(pattern, query, re.IGNORECASE)
178
  if match:
@@ -181,8 +170,7 @@ class MCPSAPClient:
181
  break
182
  except:
183
  continue
184
-
185
- # Handle relative dates
186
  if "last month" in query.lower():
187
  last_month = datetime.now() - timedelta(days=30)
188
  params["date_from"] = last_month.strftime("%Y-%m-%d")
@@ -194,11 +182,10 @@ class MCPSAPClient:
194
  params["date_from"] = yesterday.strftime("%Y-%m-%d")
195
  elif "today" in query.lower():
196
  params["date_from"] = datetime.now().strftime("%Y-%m-%d")
197
-
198
  return params
199
-
200
  def query_mcp_server(self, params: Dict[str, Any]) -> Dict[str, Any]:
201
- """Query MCP server with proper error handling"""
202
  for attempt in range(self.max_retries):
203
  try:
204
  payload = {
@@ -207,15 +194,15 @@ class MCPSAPClient:
207
  "params": params,
208
  "id": f"dynamic-{int(time.time())}"
209
  }
210
-
211
  logger.info(f"MCP Request (attempt {attempt + 1}): {json.dumps(payload, indent=2)}")
212
-
213
  response = self.session.post(
214
  MCP_URL,
215
  json=payload,
216
  timeout=30
217
  )
218
-
219
  if response.status_code == 200:
220
  result = response.json()
221
  logger.info(f"MCP Response: {json.dumps(result, indent=2)}")
@@ -227,7 +214,7 @@ class MCPSAPClient:
227
  continue
228
  else:
229
  return {"error": f"MCP server error: {response.status_code}"}
230
-
231
  except requests.exceptions.RequestException as e:
232
  logger.error(f"MCP request failed (attempt {attempt + 1}): {e}")
233
  if attempt < self.max_retries - 1:
@@ -235,65 +222,58 @@ class MCPSAPClient:
235
  continue
236
  else:
237
  return {"error": f"Connection failed: {str(e)}"}
238
-
239
  return {"error": "Max retries exceeded"}
240
-
241
  def format_sap_results(self, results: List[Dict[str, Any]]) -> str:
242
- """Format SAP results for display"""
243
  if not results:
244
- return "βœ… Query executed successfully, but no matching orders found.\\n\\nTry adjusting your criteria:\\n- Use a different date range\\n- Lower the quantity threshold\\n- Check if the SAP system has data for the specified period"
245
-
246
  formatted_results = []
247
- formatted_results.append(f"πŸ“Š **Found {len(results)} matching orders:**\\n")
248
-
249
  for i, result in enumerate(results, 1):
250
  order_id = result.get('ProcessOrderConfirmation', 'N/A')
251
  material = result.get('Material', 'N/A')
252
  quantity = result.get('ConfirmedYieldQuantity', 'N/A')
253
  unit = result.get('ConfirmedYieldQuantityUnit', '')
254
  posting_date = result.get('PostingDate', 'N/A')
255
-
256
  formatted_results.append(
257
- f"**Order {i}:**\\n"
258
- f"β€’ Order ID: {order_id}\\n"
259
- f"β€’ Material: {material}\\n"
260
- f"β€’ Quantity: {quantity} {unit}\\n"
261
- f"β€’ Posting Date: {posting_date}\\n"
262
  )
263
-
264
- return "\\n".join(formatted_results)
265
-
266
  def agent_query(self, user_query: str) -> str:
267
- """Main query processing function"""
268
  if not user_query.strip():
269
  return "❌ Please enter a query about SAP orders."
270
-
271
  try:
272
- # Step 1: Extract parameters
273
  logger.info(f"Processing query: {user_query}")
274
  params = self.extract_query_params_robust(user_query)
275
  logger.info(f"Extracted parameters: {params}")
276
-
277
- # Step 2: Query MCP server
278
  response = self.query_mcp_server(params)
279
-
280
- # Step 3: Handle response
281
  if "error" in response:
282
- return f"❌ **Error:** {response['error']}\\n\\n**Troubleshooting:**\\n- Check if your MCP server is running\\n- Verify the server URL is correct\\n- Ensure the SAP system is accessible"
283
-
284
  results = response.get("result", [])
285
-
286
- # Step 4: Format results
287
  formatted_output = self.format_sap_results(results)
288
-
289
- # Add query info
290
- query_info = f"\\n\\nπŸ“ **Query Details:**\\nβ€’ Date from: {params.get('date_from', 'N/A')}\\nβ€’ Minimum quantity: {params.get('min_quantity', 0)}"
291
-
292
  return formatted_output + query_info
293
-
294
  except Exception as e:
295
  logger.error(f"Unexpected error in agent_query: {e}")
296
- return f"❌ **Unexpected Error:** {str(e)}\\n\\nPlease try again or contact support if the issue persists."
297
 
298
  # Initialize the client
299
  client = MCPSAPClient()
@@ -312,17 +292,17 @@ def create_interface():
312
  with gr.Blocks(title="SAP MCP Agent", theme=gr.themes.Soft()) as iface:
313
  gr.Markdown("""
314
  # πŸ” SAP MCP Agent
315
-
316
  Query your SAP system naturally using the Model Context Protocol (MCP).
317
-
318
  **How to use:**
319
  - Ask about orders using natural language
320
  - Specify dates and quantities in your query
321
  - The system will extract parameters and query SAP for you
322
-
323
  **Example queries:**
324
  """)
325
-
326
  with gr.Row():
327
  with gr.Column():
328
  query_input = gr.Textbox(
@@ -330,15 +310,15 @@ def create_interface():
330
  placeholder="e.g., 'Show me orders after 2023-04-01 with at least 10 units'",
331
  lines=2
332
  )
333
-
334
  submit_btn = gr.Button("πŸ” Query SAP", variant="primary")
335
-
336
  gr.Markdown("**Example queries:**")
337
  example_buttons = []
338
  for example in example_queries:
339
  btn = gr.Button(f"πŸ“‹ {example}", size="sm")
340
  example_buttons.append(btn)
341
-
342
  with gr.Column():
343
  output = gr.Textbox(
344
  label="Results",
@@ -346,29 +326,22 @@ def create_interface():
346
  max_lines=20,
347
  show_copy_button=True
348
  )
349
-
350
  # Event handlers
351
  submit_btn.click(
352
  client.agent_query,
353
  inputs=[query_input],
354
  outputs=[output]
355
  )
356
-
357
  # Example button handlers
358
  for btn, example in zip(example_buttons, example_queries):
359
  btn.click(
360
  lambda x=example: x,
361
  outputs=[query_input]
362
  )
363
-
364
  return iface
365
 
366
- # Create and launch the interface
367
- if __name__ == "__main__":
368
- iface = create_interface()
369
- iface.launch(
370
- server_name="0.0.0.0",
371
- server_port=7860,
372
- share=True,
373
- show_error=True
374
- )
 
6
  from datetime import datetime, timedelta
7
  import logging
8
  from typing import Dict, Any, Optional, List
9
+ import os
10
 
11
  # Configure logging
12
  logging.basicConfig(level=logging.INFO)
 
28
  self.session.timeout = 30
29
  self.max_retries = 3
30
  self.retry_delay = 2
31
+
32
+ # Uncomment below if your API needs HF token and set it as a Space secret called "HF_TOKEN"
33
+ # API_TOKEN = os.getenv("HF_TOKEN", None)
34
+ # if API_TOKEN:
35
+ # self.session.headers.update({"Authorization": f"Bearer {API_TOKEN}"})
36
+
37
  def query_llm_with_fallback(self, prompt: str, max_length: int = 150) -> str:
 
38
  apis_to_try = [API_URL] + FALLBACK_APIS
39
+
40
  for api_url in apis_to_try:
41
  for attempt in range(self.max_retries):
42
  try:
43
  logger.info(f"Attempting LLM query with {api_url}, attempt {attempt + 1}")
44
+
45
  # Different payload formats for different APIs
46
  if "DialoGPT" in api_url:
47
  payload = {
 
60
  "temperature": 0.3
61
  }
62
  }
63
+
64
  response = self.session.post(
65
  api_url,
66
  json=payload,
67
  timeout=30
68
  )
69
+
70
  if response.status_code == 200:
71
  result = response.json()
 
72
  # Handle different response formats
73
  if isinstance(result, list) and len(result) > 0:
74
  if "generated_text" in result[0]:
 
80
  return result["generated_text"]
81
  elif "text" in result:
82
  return result["text"]
 
83
  return str(result)
 
84
  elif response.status_code == 503:
85
  logger.warning(f"Model loading, waiting {self.retry_delay} seconds...")
86
  time.sleep(self.retry_delay)
 
89
  logger.warning(f"API returned status {response.status_code}")
90
  time.sleep(self.retry_delay)
91
  continue
92
+
93
  except requests.exceptions.RequestException as e:
94
  logger.error(f"Request failed: {e}")
95
  if attempt < self.max_retries - 1:
96
  time.sleep(self.retry_delay)
97
  continue
98
+
99
  except Exception as e:
100
  logger.error(f"Unexpected error: {e}")
101
  if attempt < self.max_retries - 1:
102
  time.sleep(self.retry_delay)
103
  continue
104
+
105
  logger.warning(f"All attempts failed for {api_url}, trying next API...")
106
+
107
  # If all APIs fail, return a fallback response
108
  return "Unable to process with LLM, using fallback parsing"
109
+
110
  def extract_query_params_robust(self, query: str) -> Dict[str, Any]:
 
 
 
111
  try:
112
  prompt = f"""
113
  Extract date and quantity from this query: '{query}'
114
  Return only JSON format:
115
  {{"date_from":"YYYY-MM-DD","min_quantity":number}}
116
+
117
  Examples:
118
  - "orders after 2023-04-01 with at least 10 units" -> {{"date_from":"2023-04-01","min_quantity":10}}
119
  - "show me orders from last month" -> {{"date_from":"2023-11-01","min_quantity":0}}
120
  """
 
121
  llm_response = self.query_llm_with_fallback(prompt)
122
  logger.info(f"LLM Response: {llm_response}")
123
+
124
+ json_match = re.search(r'\{[^}]*\}', llm_response, re.DOTALL)
 
125
  if json_match:
126
  json_text = json_match.group()
127
  parsed = json.loads(json_text)
128
  if "date_from" in parsed and "min_quantity" in parsed:
129
  return parsed
130
+
131
  except Exception as e:
132
  logger.warning(f"LLM extraction failed: {e}")
133
+
 
134
  return self.extract_params_rule_based(query)
135
+
136
  def extract_params_rule_based(self, query: str) -> Dict[str, Any]:
 
137
  params = {"date_from": "2023-01-01", "min_quantity": 0}
 
 
138
  date_patterns = [
139
+ r'(\d{4}-\d{2}-\d{2})', # YYYY-MM-DD
140
+ r'(\d{2}/\d{2}/\d{4})', # MM/DD/YYYY
141
+ r'(\d{1,2}/\d{1,2}/\d{4})', # M/D/YYYY
142
  ]
 
143
  for pattern in date_patterns:
144
  match = re.search(pattern, query)
145
  if match:
146
  date_str = match.group(1)
147
  try:
 
148
  if '-' in date_str:
149
  params["date_from"] = date_str
150
  elif '/' in date_str:
 
154
  break
155
  except:
156
  continue
157
+
 
158
  quantity_patterns = [
159
+ r'at least (\d+)',
160
+ r'minimum (\d+)',
161
+ r'more than (\d+)',
162
+ r'> ?(\d+)',
163
+ r'greater than (\d+)'
164
  ]
 
165
  for pattern in quantity_patterns:
166
  match = re.search(pattern, query, re.IGNORECASE)
167
  if match:
 
170
  break
171
  except:
172
  continue
173
+
 
174
  if "last month" in query.lower():
175
  last_month = datetime.now() - timedelta(days=30)
176
  params["date_from"] = last_month.strftime("%Y-%m-%d")
 
182
  params["date_from"] = yesterday.strftime("%Y-%m-%d")
183
  elif "today" in query.lower():
184
  params["date_from"] = datetime.now().strftime("%Y-%m-%d")
185
+
186
  return params
187
+
188
  def query_mcp_server(self, params: Dict[str, Any]) -> Dict[str, Any]:
 
189
  for attempt in range(self.max_retries):
190
  try:
191
  payload = {
 
194
  "params": params,
195
  "id": f"dynamic-{int(time.time())}"
196
  }
197
+
198
  logger.info(f"MCP Request (attempt {attempt + 1}): {json.dumps(payload, indent=2)}")
199
+
200
  response = self.session.post(
201
  MCP_URL,
202
  json=payload,
203
  timeout=30
204
  )
205
+
206
  if response.status_code == 200:
207
  result = response.json()
208
  logger.info(f"MCP Response: {json.dumps(result, indent=2)}")
 
214
  continue
215
  else:
216
  return {"error": f"MCP server error: {response.status_code}"}
217
+
218
  except requests.exceptions.RequestException as e:
219
  logger.error(f"MCP request failed (attempt {attempt + 1}): {e}")
220
  if attempt < self.max_retries - 1:
 
222
  continue
223
  else:
224
  return {"error": f"Connection failed: {str(e)}"}
225
+
226
  return {"error": "Max retries exceeded"}
227
+
228
  def format_sap_results(self, results: List[Dict[str, Any]]) -> str:
 
229
  if not results:
230
+ return "βœ… Query executed successfully, but no matching orders found.\n\nTry adjusting your criteria:\n- Use a different date range\n- Lower the quantity threshold\n- Check if the SAP system has data for the specified period"
231
+
232
  formatted_results = []
233
+ formatted_results.append(f"πŸ“Š **Found {len(results)} matching orders:**\n")
234
+
235
  for i, result in enumerate(results, 1):
236
  order_id = result.get('ProcessOrderConfirmation', 'N/A')
237
  material = result.get('Material', 'N/A')
238
  quantity = result.get('ConfirmedYieldQuantity', 'N/A')
239
  unit = result.get('ConfirmedYieldQuantityUnit', '')
240
  posting_date = result.get('PostingDate', 'N/A')
241
+
242
  formatted_results.append(
243
+ f"**Order {i}:**\n"
244
+ f"β€’ Order ID: {order_id}\n"
245
+ f"β€’ Material: {material}\n"
246
+ f"β€’ Quantity: {quantity} {unit}\n"
247
+ f"β€’ Posting Date: {posting_date}\n"
248
  )
249
+
250
+ return "\n".join(formatted_results)
251
+
252
  def agent_query(self, user_query: str) -> str:
 
253
  if not user_query.strip():
254
  return "❌ Please enter a query about SAP orders."
255
+
256
  try:
 
257
  logger.info(f"Processing query: {user_query}")
258
  params = self.extract_query_params_robust(user_query)
259
  logger.info(f"Extracted parameters: {params}")
260
+
 
261
  response = self.query_mcp_server(params)
262
+
 
263
  if "error" in response:
264
+ return f"❌ **Error:** {response['error']}\n\n**Troubleshooting:**\n- Check if your MCP server is running\n- Verify the server URL is correct\n- Ensure the SAP system is accessible"
265
+
266
  results = response.get("result", [])
267
+
 
268
  formatted_output = self.format_sap_results(results)
269
+
270
+ query_info = f"\n\nπŸ“ **Query Details:**\nβ€’ Date from: {params.get('date_from', 'N/A')}\nβ€’ Minimum quantity: {params.get('min_quantity', 0)}"
271
+
 
272
  return formatted_output + query_info
273
+
274
  except Exception as e:
275
  logger.error(f"Unexpected error in agent_query: {e}")
276
+ return f"❌ **Unexpected Error:** {str(e)}\n\nPlease try again or contact support if the issue persists."
277
 
278
  # Initialize the client
279
  client = MCPSAPClient()
 
292
  with gr.Blocks(title="SAP MCP Agent", theme=gr.themes.Soft()) as iface:
293
  gr.Markdown("""
294
  # πŸ” SAP MCP Agent
295
+
296
  Query your SAP system naturally using the Model Context Protocol (MCP).
297
+
298
  **How to use:**
299
  - Ask about orders using natural language
300
  - Specify dates and quantities in your query
301
  - The system will extract parameters and query SAP for you
302
+
303
  **Example queries:**
304
  """)
305
+
306
  with gr.Row():
307
  with gr.Column():
308
  query_input = gr.Textbox(
 
310
  placeholder="e.g., 'Show me orders after 2023-04-01 with at least 10 units'",
311
  lines=2
312
  )
313
+
314
  submit_btn = gr.Button("πŸ” Query SAP", variant="primary")
315
+
316
  gr.Markdown("**Example queries:**")
317
  example_buttons = []
318
  for example in example_queries:
319
  btn = gr.Button(f"πŸ“‹ {example}", size="sm")
320
  example_buttons.append(btn)
321
+
322
  with gr.Column():
323
  output = gr.Textbox(
324
  label="Results",
 
326
  max_lines=20,
327
  show_copy_button=True
328
  )
329
+
330
  # Event handlers
331
  submit_btn.click(
332
  client.agent_query,
333
  inputs=[query_input],
334
  outputs=[output]
335
  )
336
+
337
  # Example button handlers
338
  for btn, example in zip(example_buttons, example_queries):
339
  btn.click(
340
  lambda x=example: x,
341
  outputs=[query_input]
342
  )
343
+
344
  return iface
345
 
346
+ # Expose interface globally for Hugging Face Spaces (do not launch explicitly)
347
+ iface = create_interface()