PD03 commited on
Commit
da1a021
Β·
verified Β·
1 Parent(s): f4d9e87

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +370 -0
app.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import openai
3
+ import requests
4
+ import json
5
+ import asyncio
6
+ import aiohttp
7
+ from typing import Dict, Any, List, Tuple
8
+ from datetime import datetime
9
+ import os
10
+
11
+ class MCPClient:
12
+ """MCP Client for communicating with the MCP server"""
13
+
14
+ def __init__(self, server_url: str):
15
+ self.server_url = server_url.rstrip('/')
16
+ self.session = None
17
+
18
+ async def initialize_session(self):
19
+ """Initialize aiohttp session"""
20
+ if not self.session:
21
+ self.session = aiohttp.ClientSession()
22
+
23
+ async def close_session(self):
24
+ """Close aiohttp session"""
25
+ if self.session:
26
+ await self.session.close()
27
+ self.session = None
28
+
29
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any] = None) -> Dict[str, Any]:
30
+ """Call a tool on the MCP server"""
31
+ if arguments is None:
32
+ arguments = {}
33
+
34
+ await self.initialize_session()
35
+
36
+ mcp_request = {
37
+ "jsonrpc": "2.0",
38
+ "id": 1,
39
+ "method": "tools/call",
40
+ "params": {
41
+ "name": tool_name,
42
+ "arguments": arguments
43
+ }
44
+ }
45
+
46
+ try:
47
+ async with self.session.post(
48
+ f"{self.server_url}/mcp",
49
+ json=mcp_request,
50
+ headers={"Content-Type": "application/json"}
51
+ ) as response:
52
+ if response.status == 200:
53
+ result = await response.json()
54
+ if "result" in result and "content" in result["result"]:
55
+ content = result["result"]["content"][0]["text"]
56
+ return json.loads(content)
57
+ return result
58
+ else:
59
+ return {
60
+ "success": False,
61
+ "error": f"HTTP {response.status}: {await response.text()}"
62
+ }
63
+ except Exception as e:
64
+ return {
65
+ "success": False,
66
+ "error": f"Connection error: {str(e)}"
67
+ }
68
+
69
+ async def list_tools(self) -> List[Dict[str, Any]]:
70
+ """List available tools on the MCP server"""
71
+ await self.initialize_session()
72
+
73
+ mcp_request = {
74
+ "jsonrpc": "2.0",
75
+ "id": 1,
76
+ "method": "tools/list"
77
+ }
78
+
79
+ try:
80
+ async with self.session.post(
81
+ f"{self.server_url}/mcp",
82
+ json=mcp_request,
83
+ headers={"Content-Type": "application/json"}
84
+ ) as response:
85
+ if response.status == 200:
86
+ result = await response.json()
87
+ return result.get("result", {}).get("tools", [])
88
+ return []
89
+ except Exception as e:
90
+ return []
91
+
92
+ class AIAssistant:
93
+ """AI Assistant with MCP integration"""
94
+
95
+ def __init__(self, openai_api_key: str, mcp_client: MCPClient):
96
+ self.openai_client = openai.OpenAI(api_key=openai_api_key)
97
+ self.mcp_client = mcp_client
98
+ self.available_tools = []
99
+
100
+ async def initialize(self):
101
+ """Initialize the assistant by fetching available tools"""
102
+ self.available_tools = await self.mcp_client.list_tools()
103
+
104
+ def get_system_prompt(self) -> str:
105
+ """Generate system prompt with available tools"""
106
+ tools_description = "\n".join([
107
+ f"- {tool['name']}: {tool['description']}"
108
+ for tool in self.available_tools
109
+ ])
110
+
111
+ return f"""You are an AI assistant with access to SAP business systems and news data through specialized tools.
112
+
113
+ Available tools:
114
+ {tools_description}
115
+
116
+ When a user asks for information that can be retrieved using these tools, you should:
117
+ 1. Identify which tool(s) would be helpful
118
+ 2. Call the appropriate tool(s) with the right parameters
119
+ 3. Interpret and present the results in a user-friendly way
120
+
121
+ For SAP-related queries (purchase orders, requisitions), use the SAP tools.
122
+ For news-related queries, use the news tools.
123
+
124
+ You can call tools by responding with: CALL_TOOL: tool_name(parameter1=value1, parameter2=value2)
125
+ """
126
+
127
+ def extract_tool_calls(self, response: str) -> List[Dict[str, Any]]:
128
+ """Extract tool calls from AI response"""
129
+ tool_calls = []
130
+ lines = response.split('\n')
131
+
132
+ for line in lines:
133
+ if line.strip().startswith('CALL_TOOL:'):
134
+ try:
135
+ tool_part = line.strip()[10:].strip()
136
+
137
+ if '(' in tool_part and ')' in tool_part:
138
+ tool_name = tool_part.split('(')[0].strip()
139
+ params_str = tool_part.split('(')[1].split(')')[0]
140
+
141
+ params = {}
142
+ if params_str.strip():
143
+ for param in params_str.split(','):
144
+ if '=' in param:
145
+ key, value = param.split('=', 1)
146
+ key = key.strip()
147
+ value = value.strip().strip('"\'')
148
+ try:
149
+ if value.isdigit():
150
+ value = int(value)
151
+ elif value.lower() in ['true', 'false']:
152
+ value = value.lower() == 'true'
153
+ except:
154
+ pass
155
+ params[key] = value
156
+
157
+ tool_calls.append({
158
+ 'name': tool_name,
159
+ 'arguments': params
160
+ })
161
+ except Exception as e:
162
+ continue
163
+
164
+ return tool_calls
165
+
166
+ async def process_message(self, user_message: str) -> Tuple[str, str]:
167
+ """Process user message and handle tool calls"""
168
+ tool_info = ""
169
+
170
+ try:
171
+ messages = [
172
+ {"role": "system", "content": self.get_system_prompt()},
173
+ {"role": "user", "content": user_message}
174
+ ]
175
+
176
+ response = self.openai_client.chat.completions.create(
177
+ model="gpt-3.5-turbo",
178
+ messages=messages,
179
+ temperature=0.7,
180
+ max_tokens=1000
181
+ )
182
+
183
+ ai_response = response.choices[0].message.content
184
+ tool_calls = self.extract_tool_calls(ai_response)
185
+
186
+ if tool_calls:
187
+ tool_results = []
188
+
189
+ for tool_call in tool_calls:
190
+ tool_info += f"πŸ”§ Calling: {tool_call['name']}\n"
191
+
192
+ result = await self.mcp_client.call_tool(
193
+ tool_call['name'],
194
+ tool_call['arguments']
195
+ )
196
+
197
+ tool_results.append({
198
+ 'tool': tool_call['name'],
199
+ 'result': result
200
+ })
201
+
202
+ if result.get('success'):
203
+ tool_info += f"βœ… {tool_call['name']} completed\n"
204
+ else:
205
+ tool_info += f"❌ {tool_call['name']} failed: {result.get('error', 'Unknown error')}\n"
206
+
207
+ tool_results_text = "\n\n".join([
208
+ f"Tool: {tr['tool']}\nResult: {json.dumps(tr['result'], indent=2)}"
209
+ for tr in tool_results
210
+ ])
211
+
212
+ final_messages = messages + [
213
+ {"role": "assistant", "content": ai_response},
214
+ {"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."}
215
+ ]
216
+
217
+ final_response = self.openai_client.chat.completions.create(
218
+ model="gpt-3.5-turbo",
219
+ messages=final_messages,
220
+ temperature=0.7,
221
+ max_tokens=1000
222
+ )
223
+
224
+ return final_response.choices[0].message.content, tool_info
225
+ else:
226
+ return ai_response, ""
227
+
228
+ except Exception as e:
229
+ return f"❌ Error processing your request: {str(e)}", ""
230
+
231
+ # Global variables
232
+ assistant = None
233
+ mcp_client = None
234
+
235
+ def test_connection(mcp_url):
236
+ """Test MCP server connection"""
237
+ if not mcp_url or mcp_url == "https://your-ngrok-url.ngrok.io":
238
+ return "❌ Please enter a valid MCP server URL"
239
+
240
+ try:
241
+ response = requests.get(f"{mcp_url.rstrip('/')}/health", timeout=10)
242
+ if response.status_code == 200:
243
+ data = response.json()
244
+ return f"βœ… Connected successfully!\nStatus: {data.get('status', 'Unknown')}\nTools: {len(data.get('available_tools', []))}"
245
+ else:
246
+ return f"❌ Connection failed: HTTP {response.status_code}"
247
+ except Exception as e:
248
+ return f"❌ Connection error: {str(e)}"
249
+
250
+ async def initialize_assistant(openai_key, mcp_url):
251
+ """Initialize the AI assistant"""
252
+ global assistant, mcp_client
253
+
254
+ if not openai_key:
255
+ return "❌ Please enter your OpenAI API key"
256
+
257
+ if not mcp_url or mcp_url == "https://your-ngrok-url.ngrok.io":
258
+ return "❌ Please enter a valid MCP server URL"
259
+
260
+ try:
261
+ mcp_client = MCPClient(mcp_url)
262
+ assistant = AIAssistant(openai_key, mcp_client)
263
+ await assistant.initialize()
264
+ return f"βœ… AI Assistant initialized with {len(assistant.available_tools)} tools available"
265
+ except Exception as e:
266
+ return f"❌ Failed to initialize: {str(e)}"
267
+
268
+ def chat_interface(message, history, openai_key, mcp_url):
269
+ """Main chat interface"""
270
+ global assistant
271
+
272
+ if not assistant:
273
+ init_result = asyncio.run(initialize_assistant(openai_key, mcp_url))
274
+ if "❌" in init_result:
275
+ history.append([message, init_result])
276
+ return history, ""
277
+
278
+ try:
279
+ response, tool_info = asyncio.run(assistant.process_message(message))
280
+
281
+ # Format response with tool info if available
282
+ if tool_info:
283
+ full_response = f"**Tool Execution:**\n{tool_info}\n\n**Response:**\n{response}"
284
+ else:
285
+ full_response = response
286
+
287
+ history.append([message, full_response])
288
+ return history, ""
289
+ except Exception as e:
290
+ error_response = f"❌ Error: {str(e)}"
291
+ history.append([message, error_response])
292
+ return history, ""
293
+
294
+ # Create Gradio interface
295
+ with gr.Blocks(title="AI Assistant with SAP & News Integration", theme=gr.themes.Soft()) as demo:
296
+ gr.Markdown("# πŸ€– AI Assistant with SAP & News Integration")
297
+ gr.Markdown("Chat with an AI that can access SAP business data and news through natural language queries.")
298
+
299
+ with gr.Row():
300
+ with gr.Column(scale=2):
301
+ chatbot = gr.Chatbot(
302
+ height=500,
303
+ show_label=False,
304
+ container=True,
305
+ bubble_full_width=False
306
+ )
307
+
308
+ msg = gr.Textbox(
309
+ placeholder="Ask me about SAP data, news, or anything else...",
310
+ show_label=False,
311
+ container=False
312
+ )
313
+
314
+ with gr.Row():
315
+ submit_btn = gr.Button("Send", variant="primary")
316
+ clear_btn = gr.Button("Clear", variant="secondary")
317
+
318
+ with gr.Column(scale=1):
319
+ gr.Markdown("### βš™οΈ Configuration")
320
+
321
+ openai_key = gr.Textbox(
322
+ label="OpenAI API Key",
323
+ type="password",
324
+ placeholder="sk-..."
325
+ )
326
+
327
+ mcp_url = gr.Textbox(
328
+ label="MCP Server URL",
329
+ value="https://your-ngrok-url.ngrok.io",
330
+ placeholder="https://abc123.ngrok.io"
331
+ )
332
+
333
+ test_btn = gr.Button("Test Connection", variant="secondary")
334
+ connection_status = gr.Textbox(label="Connection Status", interactive=False)
335
+
336
+ gr.Markdown("### πŸ“‹ Example Queries")
337
+ gr.Markdown("""
338
+ - "Show me recent purchase orders"
339
+ - "Get purchase requisitions"
340
+ - "What's the latest tech news?"
341
+ - "Get news from BBC"
342
+ - "Show me business news from the US"
343
+ """)
344
+
345
+ # Event handlers
346
+ def respond(message, history, openai_key, mcp_url):
347
+ return chat_interface(message, history, openai_key, mcp_url)
348
+
349
+ submit_btn.click(
350
+ respond,
351
+ [msg, chatbot, openai_key, mcp_url],
352
+ [chatbot, msg]
353
+ )
354
+
355
+ msg.submit(
356
+ respond,
357
+ [msg, chatbot, openai_key, mcp_url],
358
+ [chatbot, msg]
359
+ )
360
+
361
+ clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg])
362
+
363
+ test_btn.click(
364
+ test_connection,
365
+ [mcp_url],
366
+ [connection_status]
367
+ )
368
+
369
+ if __name__ == "__main__":
370
+ demo.launch()