PD03 commited on
Commit
85dc64f
Β·
verified Β·
1 Parent(s): f4ecc0a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +403 -0
app.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import openai
3
+ import requests
4
+ import json
5
+ import asyncio
6
+ import aiohttp
7
+ from typing import Dict, Any, List
8
+ from datetime import datetime
9
+ import os
10
+
11
+ # Page configuration
12
+ st.set_page_config(
13
+ page_title="AI Assistant with SAP & News Integration",
14
+ page_icon="πŸ€–",
15
+ layout="wide"
16
+ )
17
+
18
+ # Custom CSS for better UI
19
+ st.markdown("""
20
+ <style>
21
+ .main-header {
22
+ font-size: 2.5rem;
23
+ font-weight: bold;
24
+ text-align: center;
25
+ color: #1f77b4;
26
+ margin-bottom: 2rem;
27
+ }
28
+ .chat-message {
29
+ padding: 1rem;
30
+ border-radius: 0.5rem;
31
+ margin: 0.5rem 0;
32
+ }
33
+ .user-message {
34
+ background-color: #e3f2fd;
35
+ border-left: 4px solid #2196f3;
36
+ }
37
+ .assistant-message {
38
+ background-color: #f5f5f5;
39
+ border-left: 4px solid #4caf50;
40
+ }
41
+ .tool-result {
42
+ background-color: #fff3e0;
43
+ border: 1px solid #ff9800;
44
+ border-radius: 0.5rem;
45
+ padding: 1rem;
46
+ margin: 1rem 0;
47
+ }
48
+ .error-message {
49
+ background-color: #ffebee;
50
+ border: 1px solid #f44336;
51
+ border-radius: 0.5rem;
52
+ padding: 1rem;
53
+ margin: 1rem 0;
54
+ }
55
+ </style>
56
+ """, unsafe_allow_html=True)
57
+
58
+ class MCPClient:
59
+ """MCP Client for communicating with the MCP server"""
60
+
61
+ def __init__(self, server_url: str):
62
+ self.server_url = server_url.rstrip('/')
63
+ self.session = None
64
+
65
+ async def initialize_session(self):
66
+ """Initialize aiohttp session"""
67
+ if not self.session:
68
+ self.session = aiohttp.ClientSession()
69
+
70
+ async def close_session(self):
71
+ """Close aiohttp session"""
72
+ if self.session:
73
+ await self.session.close()
74
+ self.session = None
75
+
76
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any] = None) -> Dict[str, Any]:
77
+ """Call a tool on the MCP server"""
78
+ if arguments is None:
79
+ arguments = {}
80
+
81
+ await self.initialize_session()
82
+
83
+ mcp_request = {
84
+ "jsonrpc": "2.0",
85
+ "id": 1,
86
+ "method": "tools/call",
87
+ "params": {
88
+ "name": tool_name,
89
+ "arguments": arguments
90
+ }
91
+ }
92
+
93
+ try:
94
+ async with self.session.post(
95
+ f"{self.server_url}/mcp",
96
+ json=mcp_request,
97
+ headers={"Content-Type": "application/json"}
98
+ ) as response:
99
+ if response.status == 200:
100
+ result = await response.json()
101
+ if "result" in result and "content" in result["result"]:
102
+ # Extract the actual data from MCP response
103
+ content = result["result"]["content"][0]["text"]
104
+ return json.loads(content)
105
+ return result
106
+ else:
107
+ return {
108
+ "success": False,
109
+ "error": f"HTTP {response.status}: {await response.text()}"
110
+ }
111
+ except Exception as e:
112
+ return {
113
+ "success": False,
114
+ "error": f"Connection error: {str(e)}"
115
+ }
116
+
117
+ async def list_tools(self) -> List[Dict[str, Any]]:
118
+ """List available tools on the MCP server"""
119
+ await self.initialize_session()
120
+
121
+ mcp_request = {
122
+ "jsonrpc": "2.0",
123
+ "id": 1,
124
+ "method": "tools/list"
125
+ }
126
+
127
+ try:
128
+ async with self.session.post(
129
+ f"{self.server_url}/mcp",
130
+ json=mcp_request,
131
+ headers={"Content-Type": "application/json"}
132
+ ) as response:
133
+ if response.status == 200:
134
+ result = await response.json()
135
+ return result.get("result", {}).get("tools", [])
136
+ return []
137
+ except Exception as e:
138
+ st.error(f"Error listing tools: {str(e)}")
139
+ return []
140
+
141
+ class AIAssistant:
142
+ """AI Assistant with MCP integration"""
143
+
144
+ def __init__(self, openai_api_key: str, mcp_client: MCPClient):
145
+ self.openai_client = openai.OpenAI(api_key=openai_api_key)
146
+ self.mcp_client = mcp_client
147
+ self.available_tools = []
148
+
149
+ async def initialize(self):
150
+ """Initialize the assistant by fetching available tools"""
151
+ self.available_tools = await self.mcp_client.list_tools()
152
+
153
+ def get_system_prompt(self) -> str:
154
+ """Generate system prompt with available tools"""
155
+ tools_description = "\n".join([
156
+ f"- {tool['name']}: {tool['description']}"
157
+ for tool in self.available_tools
158
+ ])
159
+
160
+ return f"""You are an AI assistant with access to SAP business systems and news data through specialized tools.
161
+
162
+ Available tools:
163
+ {tools_description}
164
+
165
+ When a user asks for information that can be retrieved using these tools, you should:
166
+ 1. Identify which tool(s) would be helpful
167
+ 2. Call the appropriate tool(s) with the right parameters
168
+ 3. Interpret and present the results in a user-friendly way
169
+
170
+ For SAP-related queries (purchase orders, requisitions), use the SAP tools.
171
+ For news-related queries, use the news tools.
172
+
173
+ Always explain what you're doing and present results clearly. If a tool call fails, explain the error and suggest alternatives.
174
+
175
+ You can call tools by responding with: CALL_TOOL: tool_name(parameter1=value1, parameter2=value2)
176
+ """
177
+
178
+ def extract_tool_calls(self, response: str) -> List[Dict[str, Any]]:
179
+ """Extract tool calls from AI response"""
180
+ tool_calls = []
181
+ lines = response.split('\n')
182
+
183
+ for line in lines:
184
+ if line.strip().startswith('CALL_TOOL:'):
185
+ try:
186
+ # Parse tool call: CALL_TOOL: tool_name(param1=value1, param2=value2)
187
+ tool_part = line.strip()[10:].strip() # Remove 'CALL_TOOL:'
188
+
189
+ if '(' in tool_part and ')' in tool_part:
190
+ tool_name = tool_part.split('(')[0].strip()
191
+ params_str = tool_part.split('(')[1].split(')')[0]
192
+
193
+ # Parse parameters
194
+ params = {}
195
+ if params_str.strip():
196
+ for param in params_str.split(','):
197
+ if '=' in param:
198
+ key, value = param.split('=', 1)
199
+ key = key.strip()
200
+ value = value.strip().strip('"\'')
201
+ # Try to convert to appropriate type
202
+ try:
203
+ if value.isdigit():
204
+ value = int(value)
205
+ elif value.lower() in ['true', 'false']:
206
+ value = value.lower() == 'true'
207
+ except:
208
+ pass
209
+ params[key] = value
210
+
211
+ tool_calls.append({
212
+ 'name': tool_name,
213
+ 'arguments': params
214
+ })
215
+ except Exception as e:
216
+ st.error(f"Error parsing tool call: {e}")
217
+
218
+ return tool_calls
219
+
220
+ async def process_message(self, user_message: str) -> str:
221
+ """Process user message and handle tool calls"""
222
+ try:
223
+ # First, get AI response to understand what tools to call
224
+ messages = [
225
+ {"role": "system", "content": self.get_system_prompt()},
226
+ {"role": "user", "content": user_message}
227
+ ]
228
+
229
+ response = self.openai_client.chat.completions.create(
230
+ model="gpt-3.5-turbo",
231
+ messages=messages,
232
+ temperature=0.7,
233
+ max_tokens=1000
234
+ )
235
+
236
+ ai_response = response.choices[0].message.content
237
+
238
+ # Check if AI wants to call any tools
239
+ tool_calls = self.extract_tool_calls(ai_response)
240
+
241
+ if tool_calls:
242
+ tool_results = []
243
+
244
+ for tool_call in tool_calls:
245
+ st.info(f"πŸ”§ Calling tool: {tool_call['name']} with parameters: {tool_call['arguments']}")
246
+
247
+ result = await self.mcp_client.call_tool(
248
+ tool_call['name'],
249
+ tool_call['arguments']
250
+ )
251
+
252
+ tool_results.append({
253
+ 'tool': tool_call['name'],
254
+ 'result': result
255
+ })
256
+
257
+ # Display tool result
258
+ if result.get('success'):
259
+ st.success(f"βœ… Tool {tool_call['name']} executed successfully")
260
+ with st.expander(f"πŸ“Š {tool_call['name']} Results", expanded=False):
261
+ st.json(result)
262
+ else:
263
+ st.error(f"❌ Tool {tool_call['name']} failed: {result.get('error', 'Unknown error')}")
264
+
265
+ # Get final response with tool results
266
+ tool_results_text = "\n\n".join([
267
+ f"Tool: {tr['tool']}\nResult: {json.dumps(tr['result'], indent=2)}"
268
+ for tr in tool_results
269
+ ])
270
+
271
+ final_messages = messages + [
272
+ {"role": "assistant", "content": ai_response},
273
+ {"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."}
274
+ ]
275
+
276
+ final_response = self.openai_client.chat.completions.create(
277
+ model="gpt-3.5-turbo",
278
+ messages=final_messages,
279
+ temperature=0.7,
280
+ max_tokens=1000
281
+ )
282
+
283
+ return final_response.choices[0].message.content
284
+
285
+ else:
286
+ return ai_response
287
+
288
+ except Exception as e:
289
+ return f"❌ Error processing your request: {str(e)}"
290
+
291
+ # Streamlit App
292
+ def main():
293
+ st.markdown('<h1 class="main-header">πŸ€– AI Assistant with SAP & News Integration</h1>', unsafe_allow_html=True)
294
+
295
+ # Sidebar for configuration
296
+ with st.sidebar:
297
+ st.header("βš™οΈ Configuration")
298
+
299
+ # OpenAI API Key
300
+ openai_api_key = st.text_input(
301
+ "OpenAI API Key",
302
+ type="password",
303
+ help="Enter your OpenAI API key"
304
+ )
305
+
306
+ # MCP Server URL
307
+ mcp_server_url = st.text_input(
308
+ "MCP Server URL",
309
+ value="https://your-ngrok-url.ngrok.io",
310
+ help="Enter your ngrok URL where the MCP server is running"
311
+ )
312
+
313
+ # Test connection button
314
+ if st.button("πŸ” Test MCP Connection"):
315
+ if mcp_server_url:
316
+ try:
317
+ response = requests.get(f"{mcp_server_url.rstrip('/')}/health", timeout=10)
318
+ if response.status_code == 200:
319
+ st.success("βœ… MCP Server connected successfully!")
320
+ st.json(response.json())
321
+ else:
322
+ st.error(f"❌ Connection failed: HTTP {response.status_code}")
323
+ except Exception as e:
324
+ st.error(f"❌ Connection error: {str(e)}")
325
+ else:
326
+ st.error("Please enter MCP Server URL")
327
+
328
+ st.markdown("---")
329
+ st.markdown("### πŸ“‹ Available Commands")
330
+ st.markdown("""
331
+ - **SAP Purchase Orders**: "Show me recent purchase orders"
332
+ - **SAP Requisitions**: "Get purchase requisitions"
333
+ - **News Headlines**: "What's the latest tech news?"
334
+ - **News by Source**: "Get news from BBC"
335
+ """)
336
+
337
+ # Main chat interface
338
+ if not openai_api_key:
339
+ st.warning("⚠️ Please enter your OpenAI API key in the sidebar to continue.")
340
+ return
341
+
342
+ if not mcp_server_url or mcp_server_url == "https://your-ngrok-url.ngrok.io":
343
+ st.warning("⚠️ Please enter your MCP server URL in the sidebar.")
344
+ return
345
+
346
+ # Initialize session state
347
+ if 'messages' not in st.session_state:
348
+ st.session_state.messages = []
349
+
350
+ if 'assistant' not in st.session_state:
351
+ mcp_client = MCPClient(mcp_server_url)
352
+ st.session_state.assistant = AIAssistant(openai_api_key, mcp_client)
353
+
354
+ # Initialize assistant
355
+ async def init_assistant():
356
+ await st.session_state.assistant.initialize()
357
+
358
+ try:
359
+ asyncio.run(init_assistant())
360
+ st.success("πŸš€ AI Assistant initialized successfully!")
361
+ except Exception as e:
362
+ st.error(f"❌ Failed to initialize assistant: {str(e)}")
363
+ return
364
+
365
+ # Display chat messages
366
+ for message in st.session_state.messages:
367
+ with st.chat_message(message["role"]):
368
+ st.markdown(message["content"])
369
+
370
+ # Chat input
371
+ if prompt := st.chat_input("Ask me about SAP data, news, or anything else..."):
372
+ # Add user message
373
+ st.session_state.messages.append({"role": "user", "content": prompt})
374
+
375
+ with st.chat_message("user"):
376
+ st.markdown(prompt)
377
+
378
+ # Get assistant response
379
+ with st.chat_message("assistant"):
380
+ with st.spinner("πŸ€” Thinking and processing..."):
381
+ try:
382
+ response = asyncio.run(
383
+ st.session_state.assistant.process_message(prompt)
384
+ )
385
+ st.markdown(response)
386
+
387
+ # Add assistant response to history
388
+ st.session_state.messages.append({"role": "assistant", "content": response})
389
+
390
+ except Exception as e:
391
+ error_msg = f"❌ Sorry, I encountered an error: {str(e)}"
392
+ st.error(error_msg)
393
+ st.session_state.messages.append({"role": "assistant", "content": error_msg})
394
+
395
+ # Footer
396
+ st.markdown("---")
397
+ st.markdown(
398
+ "πŸ’‘ **Tip**: Try asking about purchase orders, requisitions, or latest news. "
399
+ "The AI will automatically use the appropriate tools to fetch the data."
400
+ )
401
+
402
+ if __name__ == "__main__":
403
+ main()