PD03 commited on
Commit
c1df0d9
·
verified ·
1 Parent(s): 3dc8a32

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -424
app.py CHANGED
@@ -3,455 +3,71 @@ import openai
3
  import requests
4
  import json
5
  from typing import Dict, Any, List, Tuple
6
- from datetime import datetime
7
- import os
8
 
9
  class MCPClient:
10
- """MCP Client for communicating with the MCP server"""
11
-
12
  def __init__(self, server_url: str):
13
  self.server_url = server_url.rstrip('/')
14
-
15
  def call_tool_sync(self, tool_name: str, arguments: Dict[str, Any] = None) -> Dict[str, Any]:
16
- """Synchronous tool call using requests instead of aiohttp"""
17
  if arguments is None:
18
  arguments = {}
19
-
20
- mcp_request = {
21
- "jsonrpc": "2.0",
22
- "id": 1,
23
- "method": "tools/call",
24
- "params": {
25
- "name": tool_name,
26
- "arguments": arguments
27
- }
28
- }
29
-
30
- try:
31
- response = requests.post(
32
- f"{self.server_url}/mcp",
33
- json=mcp_request,
34
- headers={
35
- "Content-Type": "application/json",
36
- "ngrok-skip-browser-warning": "true"
37
- },
38
- timeout=30
39
- )
40
-
41
- if response.status_code == 200:
42
- result = response.json()
43
- if "result" in result and "content" in result["result"]:
44
- content = result["result"]["content"][0]["text"]
45
- return json.loads(content)
46
- return result
47
- else:
48
- return {
49
- "success": False,
50
- "error": f"HTTP {response.status_code}: {response.text}"
51
- }
52
- except Exception as e:
53
- return {
54
- "success": False,
55
- "error": f"Connection error: {str(e)}"
56
- }
57
-
58
  def list_tools_sync(self) -> List[Dict[str, Any]]:
59
- """Synchronous tool listing using requests"""
60
- mcp_request = {
61
- "jsonrpc": "2.0",
62
- "id": 1,
63
- "method": "tools/list"
64
- }
65
-
66
- try:
67
- response = requests.post(
68
- f"{self.server_url}/mcp",
69
- json=mcp_request,
70
- headers={
71
- "Content-Type": "application/json",
72
- "ngrok-skip-browser-warning": "true"
73
- },
74
- timeout=30
75
- )
76
-
77
- if response.status_code == 200:
78
- result = response.json()
79
- return result.get("result", {}).get("tools", [])
80
- return []
81
- except Exception as e:
82
- print(f"Error listing tools: {str(e)}")
83
- return []
84
 
85
  class AIAssistant:
86
- """AI Assistant with MCP integration"""
87
-
88
  def __init__(self, openai_api_key: str, mcp_client: MCPClient):
89
- try:
90
- self.openai_client = openai.OpenAI(
91
- api_key=openai_api_key,
92
- timeout=30.0
93
- )
94
- except Exception as e:
95
- # Fallback for older OpenAI versions
96
- openai.api_key = openai_api_key
97
- self.openai_client = openai
98
  self.mcp_client = mcp_client
99
- self.available_tools = []
100
-
101
- def initialize(self):
102
- """Initialize the assistant by fetching available tools"""
103
  self.available_tools = self.mcp_client.list_tools_sync()
104
-
105
- def get_system_prompt(self) -> str:
106
- """Generate system prompt with available tools"""
107
- tools_description = "\n".join([
108
- f"- {tool['name']}: {tool['description']}"
109
- for tool in self.available_tools
110
- ])
111
-
112
- return f"""You are an AI assistant with access to SAP business systems and news data through specialized tools.
113
-
114
- Available tools:
115
- {tools_description}
116
-
117
- When a user asks for information that can be retrieved using these tools, you should:
118
- 1. Identify which tool(s) would be helpful
119
- 2. Call the appropriate tool(s) with the right parameters
120
- 3. Wait for the results before providing your final response
121
 
122
- For SAP-related queries (purchase orders, requisitions), use the SAP tools.
123
- For news-related queries, use the news tools.
124
-
125
- To call a tool, use this exact format:
126
- CALL_TOOL: tool_name
127
- or
128
- CALL_TOOL: tool_name(parameter1=value1, parameter2=value2)
129
-
130
- Examples:
131
- - For "show me purchase orders": CALL_TOOL: get_purchase_orders
132
- - For "get 20 purchase orders": CALL_TOOL: get_purchase_orders(top=20)
133
- - For "latest tech news": CALL_TOOL: get_news_headlines(category=technology)
134
 
135
- After calling a tool, I will provide you with the results to interpret for the user.
136
- """
137
-
138
  def extract_tool_calls(self, response: str) -> List[Dict[str, Any]]:
139
- """Extract tool calls from AI response"""
140
  tool_calls = []
141
  lines = response.split('\n')
142
-
143
  for line in lines:
144
- line = line.strip()
145
  if line.startswith('CALL_TOOL:'):
146
- try:
147
- # Remove 'CALL_TOOL:' prefix and clean up
148
- tool_part = line[10:].strip()
149
-
150
- # Handle cases with or without parentheses
151
- if '(' in tool_part and ')' in tool_part:
152
- tool_name = tool_part.split('(')[0].strip()
153
- params_str = tool_part.split('(')[1].split(')')[0]
154
-
155
- params = {}
156
- if params_str.strip():
157
- for param in params_str.split(','):
158
- if '=' in param:
159
- key, value = param.split('=', 1)
160
- key = key.strip()
161
- value = value.strip().strip('"\'')
162
- try:
163
- if value.isdigit():
164
- value = int(value)
165
- elif value.lower() in ['true', 'false']:
166
- value = value.lower() == 'true'
167
- except:
168
- pass
169
- params[key] = value
170
-
171
- tool_calls.append({
172
- 'name': tool_name,
173
- 'arguments': params
174
- })
175
- else:
176
- # Simple tool call without parameters
177
- tool_name = tool_part.strip()
178
- tool_calls.append({
179
- 'name': tool_name,
180
- 'arguments': {}
181
- })
182
-
183
- except Exception as e:
184
- print(f"Error parsing tool call '{line}': {e}")
185
- continue
186
-
187
  return tool_calls
188
-
189
- def process_message(self, user_message: str) -> Tuple[str, str]:
190
- """Process user message and handle tool calls"""
 
 
 
191
  tool_info = ""
192
-
193
- try:
194
- messages = [
195
- {"role": "system", "content": self.get_system_prompt()},
196
- {"role": "user", "content": user_message}
197
- ]
198
-
199
- # Check if we have a proper OpenAI client
200
- if hasattr(self.openai_client, 'chat'):
201
- response = self.openai_client.chat.completions.create(
202
- model="gpt-3.5-turbo",
203
- messages=messages,
204
- temperature=0.7,
205
- max_tokens=1000
206
- )
207
- ai_response = response.choices[0].message.content
208
- else:
209
- # Fallback for older API
210
- response = self.openai_client.ChatCompletion.create(
211
- model="gpt-3.5-turbo",
212
- messages=messages,
213
- temperature=0.7,
214
- max_tokens=1000
215
- )
216
- ai_response = response.choices[0].message.content
217
- tool_calls = self.extract_tool_calls(ai_response)
218
-
219
- # Debug information
220
- print(f"AI Response: {ai_response}")
221
- print(f"Extracted tool calls: {tool_calls}")
222
-
223
- if tool_calls:
224
- tool_results = []
225
-
226
- for tool_call in tool_calls:
227
- tool_info += f"🔧 Calling: {tool_call['name']}\n"
228
-
229
- result = await self.mcp_client.call_tool(
230
- tool_call['name'],
231
- tool_call['arguments']
232
- )
233
-
234
- tool_results.append({
235
- 'tool': tool_call['name'],
236
- 'result': result
237
- })
238
-
239
- if result.get('success'):
240
- tool_info += f"✅ {tool_call['name']} completed\n"
241
- else:
242
- tool_info += f"❌ {tool_call['name']} failed: {result.get('error', 'Unknown error')}\n"
243
-
244
- tool_results_text = "\n\n".join([
245
- f"Tool: {tr['tool']}\nResult: {json.dumps(tr['result'], indent=2)}"
246
- for tr in tool_results
247
- ])
248
-
249
- final_messages = messages + [
250
- {"role": "assistant", "content": ai_response},
251
- {"role": "user", "content": f"Here are the tool results:\n\n{tool_results_text}\n\nPlease interpret these results and provide a helpful response to the user."}
252
- ]
253
-
254
- # Get final response with tool results
255
- if hasattr(self.openai_client, 'chat'):
256
- final_response = self.openai_client.chat.completions.create(
257
- model="gpt-3.5-turbo",
258
- messages=final_messages,
259
- temperature=0.7,
260
- max_tokens=1000
261
- )
262
- return final_response.choices[0].message.content, tool_info
263
- else:
264
- final_response = self.openai_client.ChatCompletion.create(
265
- model="gpt-3.5-turbo",
266
- messages=final_messages,
267
- temperature=0.7,
268
- max_tokens=1000
269
- )
270
- return final_response.choices[0].message.content, tool_info
271
- else:
272
- return ai_response, ""
273
-
274
- except Exception as e:
275
- return f"❌ Error processing your request: {str(e)}", ""
276
 
277
- # Global variables
278
- assistant = None
279
- mcp_client = None
280
 
281
- def test_connection(mcp_url):
282
- """Test MCP server connection"""
283
- if not mcp_url or mcp_url == "https://your-ngrok-url.ngrok.io":
284
- return "❌ Please enter a valid MCP server URL"
285
-
286
- try:
287
- # Test health endpoint
288
- response = requests.get(f"{mcp_url.rstrip('/')}/health", timeout=10)
289
- if response.status_code == 200:
290
- data = response.json()
291
-
292
- # Test MCP tools list
293
- mcp_request = {
294
- "jsonrpc": "2.0",
295
- "id": 1,
296
- "method": "tools/list"
297
- }
298
-
299
- mcp_response = requests.post(
300
- f"{mcp_url.rstrip('/')}/mcp",
301
- json=mcp_request,
302
- headers={
303
- "Content-Type": "application/json",
304
- "ngrok-skip-browser-warning": "true"
305
- },
306
- timeout=10
307
- )
308
-
309
- if mcp_response.status_code == 200:
310
- mcp_data = mcp_response.json()
311
- tools = mcp_data.get("result", {}).get("tools", [])
312
- tool_names = [tool.get("name", "Unknown") for tool in tools]
313
-
314
- return f"✅ Connected successfully!\nHealth Status: {data.get('status', 'Unknown')}\nMCP Tools: {len(tools)}\nAvailable: {', '.join(tool_names)}"
315
- else:
316
- return f"✅ Health OK, but MCP endpoint failed: HTTP {mcp_response.status_code}"
317
- else:
318
- return f"❌ Connection failed: HTTP {response.status_code}"
319
- except Exception as e:
320
- return f"❌ Connection error: {str(e)}"
321
 
322
- def initialize_assistant(openai_key, mcp_url):
323
- """Initialize the AI assistant"""
324
- global assistant, mcp_client
325
-
326
- if not openai_key:
327
- return "❌ Please enter your OpenAI API key"
328
-
329
- if not mcp_url or mcp_url == "https://your-ngrok-url.ngrok.io":
330
- return "❌ Please enter a valid MCP server URL"
331
-
332
- try:
333
- mcp_client = MCPClient(mcp_url)
334
- assistant = AIAssistant(openai_key, mcp_client)
335
- assistant.initialize()
336
- return f"✅ AI Assistant initialized with {len(assistant.available_tools)} tools available"
337
- except Exception as e:
338
- return f"❌ Failed to initialize: {str(e)}"
339
 
340
- def chat_interface(message, history, openai_key, mcp_url):
341
- """Main chat interface"""
342
- global assistant
343
-
344
- if not assistant:
345
- init_result = initialize_assistant(openai_key, mcp_url)
346
- if "❌" in init_result:
347
- history.append([message, init_result])
348
- return history, ""
349
-
350
- try:
351
- print(f"Calling process_message with: {message}")
352
-
353
- # Make sure we call the synchronous method
354
- result = assistant.process_message(message)
355
- print(f"process_message returned: {type(result)} - {result}")
356
-
357
- # Check if result is a tuple (response, tool_info)
358
- if isinstance(result, tuple) and len(result) == 2:
359
- response, tool_info = result
360
- print(f"Unpacked: response={response}, tool_info={tool_info}")
361
- else:
362
- response = str(result)
363
- tool_info = ""
364
- print(f"Single result: {response}")
365
-
366
- # Format response with tool info if available
367
- if tool_info:
368
- full_response = f"**Tool Execution:**\n{tool_info}\n\n**Response:**\n{response}"
369
- else:
370
- full_response = response
371
-
372
- history.append([message, full_response])
373
- return history, ""
374
- except Exception as e:
375
- import traceback
376
- error_response = f"❌ Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
377
- print(f"Error in chat_interface: {error_response}")
378
- history.append([message, error_response])
379
- return history, ""
380
 
381
- # Create Gradio interface
382
- with gr.Blocks(title="AI Assistant with SAP & News Integration", theme=gr.themes.Soft()) as demo:
383
- gr.Markdown("# 🤖 AI Assistant with SAP & News Integration")
384
- gr.Markdown("Chat with an AI that can access SAP business data and news through natural language queries.")
385
-
386
- with gr.Row():
387
- with gr.Column(scale=2):
388
- chatbot = gr.Chatbot(
389
- height=500,
390
- show_label=False,
391
- container=True,
392
- bubble_full_width=False
393
- )
394
-
395
- msg = gr.Textbox(
396
- placeholder="Ask me about SAP data, news, or anything else...",
397
- show_label=False,
398
- container=False
399
- )
400
-
401
- with gr.Row():
402
- submit_btn = gr.Button("Send", variant="primary")
403
- clear_btn = gr.Button("Clear", variant="secondary")
404
-
405
- with gr.Column(scale=1):
406
- gr.Markdown("### ⚙️ Configuration")
407
-
408
- openai_key = gr.Textbox(
409
- label="OpenAI API Key",
410
- type="password",
411
- placeholder="sk-..."
412
- )
413
-
414
- mcp_url = gr.Textbox(
415
- label="MCP Server URL",
416
- value="https://your-ngrok-url.ngrok.io",
417
- placeholder="https://abc123.ngrok.io"
418
- )
419
-
420
- test_btn = gr.Button("Test Connection", variant="secondary")
421
- connection_status = gr.Textbox(label="Connection Status", interactive=False)
422
-
423
- gr.Markdown("### 📋 Example Queries")
424
- gr.Markdown("""
425
- - "Show me recent purchase orders"
426
- - "Get purchase requisitions"
427
- - "What's the latest tech news?"
428
- - "Get news from BBC"
429
- - "Show me business news from the US"
430
- """)
431
-
432
- # Event handlers
433
- def respond(message, history, openai_key, mcp_url):
434
- return chat_interface(message, history, openai_key, mcp_url)
435
-
436
- submit_btn.click(
437
- respond,
438
- [msg, chatbot, openai_key, mcp_url],
439
- [chatbot, msg]
440
- )
441
-
442
- msg.submit(
443
- respond,
444
- [msg, chatbot, openai_key, mcp_url],
445
- [chatbot, msg]
446
- )
447
-
448
- clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg])
449
-
450
- test_btn.click(
451
- test_connection,
452
- [mcp_url],
453
- [connection_status]
454
- )
455
 
456
- if __name__ == "__main__":
457
- demo.launch()
 
3
  import requests
4
  import json
5
  from typing import Dict, Any, List, Tuple
 
 
6
 
7
  class MCPClient:
 
 
8
  def __init__(self, server_url: str):
9
  self.server_url = server_url.rstrip('/')
10
+
11
  def call_tool_sync(self, tool_name: str, arguments: Dict[str, Any] = None) -> Dict[str, Any]:
 
12
  if arguments is None:
13
  arguments = {}
14
+
15
+ mcp_request = {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": tool_name, "arguments": arguments}}
16
+ response = requests.post(f"{self.server_url}/mcp", json=mcp_request, headers={"Content-Type": "application/json"})
17
+ return response.json()
18
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  def list_tools_sync(self) -> List[Dict[str, Any]]:
20
+ response = requests.post(f"{self.server_url}/mcp", json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"}, headers={"Content-Type": "application/json"})
21
+ return response.json().get("result", {}).get("tools", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  class AIAssistant:
 
 
24
  def __init__(self, openai_api_key: str, mcp_client: MCPClient):
25
+ openai.api_key = openai_api_key
 
 
 
 
 
 
 
 
26
  self.mcp_client = mcp_client
 
 
 
 
27
  self.available_tools = self.mcp_client.list_tools_sync()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ def get_system_prompt(self) -> str:
30
+ return f"Available tools: {[tool['name'] for tool in self.available_tools]}"
 
 
 
 
 
 
 
 
 
 
31
 
 
 
 
32
  def extract_tool_calls(self, response: str) -> List[Dict[str, Any]]:
 
33
  tool_calls = []
34
  lines = response.split('\n')
 
35
  for line in lines:
 
36
  if line.startswith('CALL_TOOL:'):
37
+ tool_name = line.split(':')[1].strip()
38
+ tool_calls.append({'name': tool_name, 'arguments': {}})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  return tool_calls
40
+
41
+ async def process_message(self, user_message: str) -> Tuple[str, str]:
42
+ messages = [{"role": "system", "content": self.get_system_prompt()}, {"role": "user", "content": user_message}]
43
+ response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages)
44
+ ai_response = response.choices[0].message.content
45
+
46
  tool_info = ""
47
+ tool_calls = self.extract_tool_calls(ai_response)
48
+ if tool_calls:
49
+ for tool_call in tool_calls:
50
+ result = self.mcp_client.call_tool_sync(tool_call['name'])
51
+ tool_info += f"Called {tool_call['name']}: {result}\n"
52
+ ai_response += f"\n\nTool results:\n{tool_info}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ return ai_response, tool_info
 
 
55
 
56
+ async def chat_interface(message, history, openai_key, mcp_url):
57
+ assistant = AIAssistant(openai_key, MCPClient(mcp_url))
58
+ response, tool_info = await assistant.process_message(message)
59
+ history.append([message, response])
60
+ return history, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ with gr.Blocks() as demo:
63
+ chatbot = gr.Chatbot()
64
+ msg = gr.Textbox()
65
+ openai_key = gr.Textbox(label="OpenAI API Key")
66
+ mcp_url = gr.Textbox(label="MCP Server URL")
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ submit_btn = gr.Button("Send")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ submit_btn.click(chat_interface, [msg, chatbot, openai_key, mcp_url], [chatbot, msg])
71
+ msg.submit(chat_interface, [msg, chatbot, openai_key, mcp_url], [chatbot, msg])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ demo.launch()