PD03 commited on
Commit
a1d5fa8
Β·
verified Β·
1 Parent(s): 0675905

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +363 -56
app.py CHANGED
@@ -1,67 +1,374 @@
1
  import gradio as gr
2
- import requests, json, re
 
 
 
 
 
 
3
 
 
 
 
 
 
4
  MCP_URL = "https://pretty-areas-make.loca.lt/mcp"
 
5
 
6
- # Verified public endpoint for Falcon-7B-Instruct
7
- API_URL = "https://falcon-7b-instruct-demo.hf.space/run/predict"
 
 
 
8
 
9
- def query_llm(prompt):
10
- response = requests.post(API_URL, json={"data": [prompt]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- if response.status_code == 200:
13
- result = response.json()
14
- return result['data'][0]
15
- else:
16
- raise ValueError(f"API Error: {response.status_code}")
17
-
18
- def extract_query_params(query):
19
- prompt = f"""
20
- Extract date and quantity from '{query}'.
21
- Return ONLY JSON:
22
- {{"date_from":"YYYY-MM-DD","min_quantity":number}}
23
- """
24
- llm_response = query_llm(prompt)
25
-
26
- try:
27
- json_text = re.search(r"\{.*\}", llm_response, re.DOTALL).group()
28
- return json.loads(json_text)
29
- except:
30
- return {"date_from":"2023-01-01","min_quantity":0}
31
-
32
- def agent_query(user_query):
33
- try:
34
- params = extract_query_params(user_query)
35
- payload = {
36
- "jsonrpc":"2.0",
37
- "method":"SAP.getFilteredProcOrder",
38
- "params":params,
39
- "id":"dynamic-001"
40
- }
41
- response = requests.post(MCP_URL,json=payload).json()
42
- results = response.get("result", [])
43
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  if not results:
45
- return "No matching orders found."
46
-
47
- formatted = "\n\n".join(
48
- f"Order ID: {r.get('ProcessOrderConfirmation','N/A')}, Material: {r.get('Material','N/A')}, "
49
- f"Qty: {r.get('ConfirmedYieldQuantity','N/A')} {r.get('ConfirmedYieldQuantityUnit','')}, "
50
- f"Posting: {r.get('PostingDate','N/A')}"
51
- for r in results
52
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- return formatted
 
55
 
56
- except Exception as e:
57
- return f"Error: {str(e)}"
 
 
 
 
 
 
58
 
59
- iface = gr.Interface(
60
- fn=agent_query,
61
- inputs="text",
62
- outputs="text",
63
- title="Dynamic MCP Agent for SAP (Public Falcon LLM)",
64
- description="Ask SAP queries naturally, e.g., 'Orders after 2023-04-01 with at least 10 units.'"
65
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- iface.launch()
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import requests
3
+ import json
4
+ import re
5
+ 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)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Configuration
15
  MCP_URL = "https://pretty-areas-make.loca.lt/mcp"
16
+ API_URL = "https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium"
17
 
18
+ # Fallback API URLs in case primary fails
19
+ FALLBACK_APIS = [
20
+ "https://api-inference.huggingface.co/models/microsoft/DialoGPT-small",
21
+ "https://api-inference.huggingface.co/models/gpt2"
22
+ ]
23
 
24
+ class MCPSAPClient:
25
+ def __init__(self):
26
+ self.session = requests.Session()
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 = {
43
+ "inputs": prompt,
44
+ "parameters": {
45
+ "max_new_tokens": max_length,
46
+ "temperature": 0.3,
47
+ "return_full_text": False
48
+ }
49
+ }
50
+ else:
51
+ payload = {
52
+ "inputs": prompt,
53
+ "parameters": {
54
+ "max_length": max_length,
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]:
71
+ return result[0]["generated_text"]
72
+ elif "text" in result[0]:
73
+ return result[0]["text"]
74
+ elif isinstance(result, dict):
75
+ if "generated_text" in result:
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)
85
+ continue
86
+ else:
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:
160
+ parts = date_str.split('/')
161
+ if len(parts) == 3:
162
+ params["date_from"] = f"{parts[2]}-{parts[0].zfill(2)}-{parts[1].zfill(2)}"
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:
179
+ try:
180
+ params["min_quantity"] = int(match.group(1))
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")
189
+ elif "last week" in query.lower():
190
+ last_week = datetime.now() - timedelta(days=7)
191
+ params["date_from"] = last_week.strftime("%Y-%m-%d")
192
+ elif "yesterday" in query.lower():
193
+ yesterday = datetime.now() - timedelta(days=1)
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 = {
205
+ "jsonrpc": "2.0",
206
+ "method": "SAP.getFilteredProcOrder",
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)}")
222
+ return result
223
+ else:
224
+ logger.warning(f"MCP server returned status {response.status_code}: {response.text}")
225
+ if attempt < self.max_retries - 1:
226
+ time.sleep(self.retry_delay)
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:
234
+ time.sleep(self.retry_delay)
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()
300
 
301
+ # Example queries for users
302
+ example_queries = [
303
+ "Show me orders after 2023-04-01 with at least 10 units",
304
+ "Orders from last month with minimum 5 quantity",
305
+ "Find orders from 2023-06-15 with at least 20 units",
306
+ "Show me all orders from last week",
307
+ "Orders with quantity greater than 50 from 2023-05-01"
308
+ ]
309
 
310
+ # Create Gradio interface
311
+ 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(
329
+ label="Enter your SAP query",
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",
345
+ lines=15,
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
+ )